In this chapter we're going to continue the UI development from the last chapter. We pretty much completed the messages container at that point but we do have some things missing like the fragment selection and the loading state. But after that we're going to focus on the Project Header component, which is the component above the messages container, which will tell us which is the currently active project and the buttons to go back. So let's go ahead and first handle the leftovers from the previous chapter. As always, make sure that you're on your main branch and click on synchronize changes just to confirm everything is up to date.
So now what I want to do is I want to go inside of my project view inside of projects UI views project view And in here, let's go ahead and let's introduce an active fragment and setActiveFragmentState. From useState, and by default, let's set it to null. And the type can be a type of fragment from Prisma or null. So just make sure you added these imports here. Once you've added that, Let's go ahead and let's modify the messages container component to have a few more props.
Let's add active fragment to be active fragment. And let's add set active fragment to be set active fragment. Now go inside of the messages container and let's improve these props. So I'm going to add the active fragment prop to be fragment or null and make sure to import the fragment and add the set active fragment right here. And then you can extract them in the new props here.
Active fragment and set active fragment. Just like that. And then inside of your use useEffect here, if we detect the lastAssistantMessage, call setActiveFragment and setLastAssistantMessage, my apologies, lastAssistantMessage.fragment inside, but only if we have last assistant message fragment. Well, actually, since it's going to be null... Yeah, let's go ahead and we can just do this.
It's okay. And call this. There we go. So now, one of the fragments will always be selected. What we have to do now is we have to go to the message card and set the active fragment question mark dot ID to be identical to message dot fragment question mark ID and set active fragment will on fragment click will call set active fragment and pass the message dot fragment inside like this.
So now inside of your project here when you click on a specific fragment it should be highlighted like this. Perfect. And when you load the page, since this is an error right now, nothing is highlighted here. But if you try this again, build a landing page for example, I'm going to wait for a second for this to respond and you're going to see that then when you refresh it will automatically select that fragment thanks to this use effect right here which searches for the last message which role is assistant And perhaps we can even improve this by searching for the last assistant message with fragment. And then we can do this and message.fragment and just turn this into a Boolean.
And then just do this. So you can see that now when I refresh this fragment is automatically selected. Perfect, exactly what we need. So now that we have that let's also create a loading state. In order to do that, let's go outside of the use effect here.
Let's create a constant to find the last message. Instead of data, let's use messages, like this. And then we are going to find the last user message. So if if is last message user, so if last message role is user, it means that we are the one who sent the message last. So that's going to be the system we are going to rely on for now to display loading.
Later we can improve it more. So let's do this. Let's go just above the bottom ref and let's do if lastMessage is user add message loading state. Like this. Now let's create message loading dot dsx here.
And in here, this is what we're going to do. So import image from next image and import use state and use useEffect from React. Now in here, first define shimmerMessages function. And in here, add an array of messages. This can be anything you want.
So I'm going to add thinking, loading, generating, analyzing your request, building your website, crafting components, basically things like that. And then what I'm going to do is I'm going to create a state or current message index and set current message index with the initial value of 0. And then I'm going to create a use effect here like this. And the use effect will do the following. It will create an interval, set interval and inside of this interval, every two seconds, I'm going to call setCurrentMessageIndex previous, previous plus one, modulus, messages.length.
Like that. And inside of here, I'm going to add messages.length. And in the return method here all all clearInterval and pass the interval constant. Like this. And then inside of here, you're going to return a div and a span and inside, render the currently active message.
Like this. Now give this span a class name of TextBase, TextMutedForeground, and animate false. And give the outer div a class name of flex-items-center and the gap of 2. Like this. And now finally, let's export const message-loading.
Inside of here, we are going to return a div with a class name flex flex column group exf2 and padding bottom of 4. Then a div of class name flex items center gap2 pl2 and margin bottom of 2. Then we're going to render an image component with a source of logo, svg, alp of our project name, width of 18, height of 18 as well, and the class name of shrink 0. After that a span with the name of our project, with the class name text small and font medium. Outside of this div we're going to open a new one with the class name PL8.5, flex, flex-column, and getY of 4.
And inside, render the ShimmerMessages component. And then inside of the messages container here, you can import message loading component. Like this. So now if you try and do build a yellow landing page, you will see this. Thinking, loading, generating, analyzing your request.
So something for the user to look at while this generates. And if you really want to immediately see the results of this, so Right now we have to refresh, we have to wait for some kind of refetch. What you can actually do inside of your messages container is you can add a refetch interval, for example, every five seconds. So now even without you refreshing, it's going to refetch the messages every five seconds. And there we go, we get a result.
So we can add a to-do here. Temporary live message update. Like this. But just so you can start showing this to people so you don't have to refresh your page every time so yes now if you take a look at your network request every five seconds there will be a network for refreshing the messages but don't worry since we are using react query a lot of this will be cached. Great.
So now let's go ahead and let's build a component which will be above this and it will be used to display the project name and the ability to go back. So I'm going to go back inside of the project view component and just above the suspense for loading messages I am going to add project header component. I'm going to pass project ID to be project ID like this. And after you've done that let's go inside of components and let's create project project-header.tsx like this. Now inside of here, let's go ahead and add the following imports.
Link, image, used theme from next themes, so you already have this inside of your package.json. This will be used to enable dark mode. Use suspense query from 10 stack React query. Some icons, chevron down, chevron left, edit, sun, moon icon. And then let's add use trpc from trpc client, button from components UI button.
And all of these imports from the drop-down menu. The menu itself, Content, Item, Portal, Radio Group, Radio Item, Separator, Sub, Sub Content, Sub Trigger, and Menu Trigger. All of those things. Now let's go ahead and let's create an interface props here. And let's go ahead and define project header right here.
Now, when we are inside of here, we can add trpc use trpc. And we can go ahead and fetch our project using use suspense query trpc projects get one query options ID project ID. And now we've done what we did initially, right? Remember, we had the project loading here, but now we moved it here. So it's time to do the following.
First import the project header from ..components project header and after that wrap it in its own suspense. Like this. And give it a fallback offloading project. Like this. And now that we have this, let's go ahead and add a header tag right here.
Let's give it a class name of padding2 flex justify between items center and border bottom. And let's call it header. And let me just refresh here. And it looks like it's not showing. Now, it is, but it is very small, I believe.
So let's go ahead and let me see. Let's, oh, my apologies. No, it is not visible. We're not rendering anything. I thought it was just very small, but it didn't make sense.
Make sure to return it. There we go. Now we can see header in the text there we go perfect so now let's go ahead and develop this header even further so I'm going to add a drop down menu here we have all of these components imported Now inside of here add a drop-down menu trigger and give it an as-child property. This will allow it to become the button which is inside. And then let's give this button a variant of ghost, a size, of small, a class name, of focus, visible, ring, zero, hover, bg, transparent, hover, opacity, 75, transition, opacity and PL2 with an exclamation point at the end.
In Tailwind, this means important. We are basically overriding some classes. In here, you're going to render the image with the source of logo.svg out of the project name width of 18 height of 18 then a span element with the project name the project name coming from the query which we just loaded This will have a class name of text, small and font medium. After that a chevron down icon. And there we go.
This now becomes a drop down menu. It doesn't have the proper cursor but don't worry we will fix that later. Great. So now let's go ahead and go outside of the drop-down menu trigger, and let's add drop-down menu content. And let's give this a side of bottom.
Let's give this an align of start. Let's get the drop down menu item here. Let's give it an as child property. Let's make sure we close the drop-down menu item component. Add a link component here.
Give it an href to the root page. Add the chevron left icon and a span element, go to dashboard. And there we go. Now the first item is to go back. Perfect.
Now we have a way to go to the landing page. What I want to do next is I want to create a drop down menu separator. So let's do drop down menu separator here. There we go. And below that, add a drop down menu sub and then a drop down menu sub trigger.
Give this a class name of gap2. Inside of this trigger, render a sun-moon icon. Give this a class name of size4 and text muted foreground. And then a span with the text appearance. And now you have a sub menu here.
And Now let's go ahead and go outside of the trigger and add drop down menu portal. Inside of the portal, add drop down menu subcontent. Inside of subcontent, add drop down menu radio group. Give it a value for now of light. And on value change of an empty arrow function for now.
Now let's add drop-down menu radio item. Give this a value of light and render a span light. Now go ahead and copy this two times. The second one will be dark with the text dark. The third one will be system with the text system like this.
And now you will have the option to select different themes. In order to enable this, we first have to go inside of our layout, our main layout, in the app folder next to the root page, right? So this one with the body and everything. And then in here, add to the HTML tag, suppress hydration warning. And then inside of body, add a theme provider from next themes and encapsulate the toaster and the children.
So just make sure you have added the import here. Let me just move this up here. Oops, looks like I did something incorrectly here. Let me just do it again. So I'm going to add ThemeProvider and encapsulate the children.
Now inside of here, I'm going to give it an attribute class, I'm going to give it a default theme of system and I'm going to give it the enable system option as well as disable transition on change. And now let's go inside of project header back and in here I'm going to add const setTheme and theme from useTheme. You have imported this from nextThemes. You can remove the edit icon. And now let's go back to our radio here, set the value to be theme, change this to be a set theme And I think that is pretty much it.
If you try clicking on dark mode, it should use the dark mode. Try refreshing if it doesn't work. There we go. Perfect. We now have dark mode.
We will of course improve the look of it later, but pretty impressive so far. Great. So that marks the end of the project header for now. What we're gonna do or start doing in the next chapter will be previewing the actual fragments and fix any potential issues that we have. This will also include creating the code editor, right?
Amazing job. So let's go ahead. I can see that we have some issue here. Every time I select this fragment, very soon the bottom one starts to select. So I'm pretty sure that something inside of my messages container...
Oh yes, the refetch interval is probably causing this to refetch every time. So maybe a better option for now would be to not use it. So I'm going to comment it out. I will add to do this is causing problems. Yes, it's definitely that refetch interval.
So now by default, no fragment is selected, only you can select it. It's okay to be like that now. Great! In the next chapter we are developing this. So let's go ahead and do what we usually do.
Let's mark what we completed And let's open a new branch. Project header. So I'm going to open a new branch here. Create new branch. 11 project header.
I'm going to stage all of my changes. 11 project header. I'm going to commit and I'm going to publish this branch. Then I'm going to go ahead and go in my GitHub. And I'm going to open a new pull request so that we can review all the things we did.
And here we have the summary. New features. We added a dynamic project header with theme switching and navigation options. We also introduced a loading indicator with animated messages during message processing. We enabled live updates for messages with automatic refreshing every 5 seconds.
We improved message interaction by highlighting and managing active message fragments. Perfect, that is exactly what we did in this chapter. As always in here we have file by file walkthrough and of course a sequence diagram, this time including the periodically refetching messages which we just added. Amazing! And as for the comments, we are very good again, no comments except some nitpick comments.
Amazing job! Let's go ahead and let's merge this and after you have merged it, go back to your project, change to the main branch and make sure to synchronize your changes. After you have synchronized your changes as always you can click on the source control graph and confirm that you have just merged chapter 11. And I believe That marks the end of this chapter. Amazing, amazing job and see you in the next one.