So let's go ahead and let's create this info component right here. So I wanted to have a logo of my application that I can click and then I can go back to all boards. I wanted to have the title of my board and I wanted to have additional menu right here in the corner. So let's find the info component which is inside of our app folder, board, board ID components, info and first things first let's mark this as use client so we can add some interactive stuff to this component and let's see so once I've added this it seems to broke my app can I just refresh can I restart my app I'm pretty sure this should not be breaking my app? Or, oh, maybe I think I know what this might be.
Okay, so make sure you have NPX convex dev running, make sure you have NPM run dev running. Let's see if this is still an issue. I think I might have an idea. Yeah, okay. I think that the issue is that we are using this and then we are doing that inside of loading here.
So I think that I have to mark loading as use client. Does that fix it? There we go. That fixes it. Yeah, so when you're exporting loading functions like this and that is from a client component, you cannot import that in a server component like this.
So usually you can import client components inside of server but it looks like when you destructure it like this it breaks the app. So the alternative is that you can do things like export const info skeleton. Something like this. Let me just try it out. So I can export info skeleton separately.
And then in here, I would import info skeleton directly. And then I think that I can remove UseClient from loading and I think everything should still work fine. Yeah, this doesn't break the app. So I really want to do that rather than this kind of export because it just feels like it's breaking my app. So I'm not going to add use client to the loading and I'm not going to use info.skeleton instead I'm going to export it manually.
Now I'm going to go inside of the toolbar and do the same thing. Because I feel like I'm breaking some rules of React server and client components here. So let's export const toolbar skeleton. It's a very simple refactor. So we just turn this into an arrow function and a constant and then in here we directly import the toolbar skeleton.
So let's just add that and let's go inside of participants and do the same thing so this will be export const participants skeleton very simple go back inside of here and import participants skeleton. There we go, so interesting bug, but luckily we resolved it quite quickly. So there we go. Now our loading can be a server component. It doesn't matter and we can mix and match server and client components instead.
I just feel like that's a safer thing to do. Alright, let's go back inside of our info component right here. And this is now marked as use client so what I want to do is I want to create an interface info props to accept the board ID which is a string and then let's add that here info props board ID and then inside we're gonna have to fetch the current board using convex use query. But before we do that, let's actually go inside of canvas where we render the info component. We no longer need this console log and we no longer need this use self hook.
We just did that to test out auth. So let's pass in the board ID to this component right here. Remember we have this in the props because inside of the page.tsx we pass that using params like that. Right, so make sure it's in the canvas, make sure it's in the info and then you should have it here. And then let's do const data is going to be useQuery from convex-react, it's going to use API from convex generated API it's gonna target API board get and we're gonna go ahead and pass in ID to be board ID as ID from convex generated data model and specify that it's an ID of boards like this.
So this API board get is that simple function query we created when we needed it for the out of the room. So go back inside of here. And now I'm simply gonna write, if there is no data, we can just return info skeleton. Otherwise, let's go ahead and let's actually add some information here. So I'm gonna go ahead and import image from next image.
I'm going to import Poppins from next font to Google. I'm going to import CN from libutils. I'm going to import button component from components UI button and I think for now that should be all right. So let me just quickly define my font here. I think I already have that somewhere in org sidebar.
That's right in organization sidebar I have this. So let me just copy it and paste that here. Alright, so now we have this in our info component. And now let me remove this text saying to do information about the board and instead let me add a button component. Let me add an image here.
I'm gonna pass in the source to be slash logo SVG. The alt is going to be logo. The height is going to be 40 and the width is going to be 40 as well. And now I should have my logo here in the button. So the same way you did in the organization sidebar, right?
Slash logo.svg. And if you want for an alt, it might be better to like be more specific and say your company or your app name logo. And now let's go ahead and style this button by giving it a property class name, PX2, like that. And I want to create a separate variant for my button whenever it is used inside of a canvas board. So let's go inside, let me just, I have too many stuff open.
Let's go inside of components, UI button, and we're going to add a new variant here. So the last one for me is Link. So I'm gonna add Board. So that's one is gonna be on hover BG Blue 500 with a 20% opacity and also it's gonna change the text to Blue 800. And we're gonna have Board active which is very simply gonna be BG blue 520 and text blue 800.
So without the hover effect. Now we can go back inside of our info component where we just were. And let's give that new variant to our button. So variant board There we go. You can see how now when I hover it has a nice little effect and it fits nicely inside of this whole canvas design Great, so we now have that and besides this image I also want to render the name of my application so let's render that in a span the name of our app and let's give this a class name cn so it's going to be dynamic let's write font semi-bold text excel margin left 2 and text black actually we don't need explicit text black actually we do because it's inside of that button.
I don't want this one to change the color, so I'm going to override it. And let's add font.classname so it takes that poppins font. And there we go. So just make sure that you have the font constant here, poppins, which we imported from NextFontGoogle. And there we go.
This is what that looks like. Very, very nice. And now what I want to do is that when I click on this I want it to redirect me back to all of my boards so for that we're gonna be using link from next link link like that And let's go ahead and wrap everything inside of the button with a link component. So including both the image and the span like this. And the href is very simply gonna go to a root slash.
And in order not to break the button, we have to pass in as child prop when we use the link inside of it. There we go. So now when I click here, I should be redirected back to my boards. Perfect. And now I want to add a little popover indicator, like a tooltip, to tell the user what's going to happen once they click on this.
So for that, we're going to use our reusable hint component. So let's import hint from components hint. And let's go ahead and wrap the entire button in a hint component. Like that. Let's go ahead and give it a label, go to boards.
So now if I hover, there we go, it says go to boards. And let me just position it slightly better. So I'm going to give it a side of bottom explicitly and side offset of 10. Like that. And I think now this looks very very nice.
Beautiful. And now let's go ahead and render the actual board name that we are in. So for that I want to create one small reusable component called tab separator. And let's go ahead and we don't need to export it. So it's just gonna be used here.
And let's return a div, which is gonna render a pipe and the class name text neutral 300 and BX 1.5 like this. And you can actually render it above. It really doesn't matter. I don't know why I'm changing it. It doesn't matter where you render it.
Okay, and now let's just use that right after our hint. Let's add the tab separator like that. So you can see that now we have this nice little separator here. And now let's go ahead and add a button again. But this time it's going to render data.title, so the name of your board.
Let's go ahead and give this a variant of board as well and a class name of text base.normal and px2 like that. There we go. So now you can see how it has the name of my board and it matches the name here. So if I go ahead and rename this and click here again, now it says rename. And I want this exact thing, this pop-up to happen once I click on this name board right here.
And because we made our rename model programmatically controlled using zustand hooks we can now easily open it up by simply calling this useRenameModel() on open function. So let's go ahead and import that. I'm going to import username model from hooks, sorry from store username model and in here I'm going to go ahead and call const on open use rename model like this and then I'm gonna give this button an onClick here it's gonna call that onOpen and it's gonna pass in data underscore id and data dot title just like this and make sure that you're rendering something if data is not available otherwise I think you're gonna have to put question marks here. And let's try this out. Perfect.
So can I change this? Real-time immediately reflected here. That's the power of convex real-time database. Perfect. Also, this also has like a character limit if you didn't notice.
Yeah, because we added, we used native HTML character limit and it also has front-end validation just in case you were wondering. Perfect! So this is working. And last thing we need to add is our menu to copy the board link and to delete the board. And we don't have to do much either because we already have our actions component which we can reuse.
But just before we do that I also want to tell the user with a little hint component what this button will do. So it's gonna be very simple. It's edit title. Side is gonna be explicitly to the bottom and side offset is going to be 10 like this so if you try now, there we go, you can see how it matches that perfect, so let's go ahead and now and let's import actions from components actions which is just another one of our reusable components we use this one in board card index if you remember so in here we have actions we pass in id title and we also have some side properties. So we already use that here, if you're wondering.
Let's go back inside of our info now. So at the end of this hint, let's add another tab separator here. And then let's render our actions component. And the actions component needs a child element, so it's not a self-closing tag. So let's pass in to the actions ID to be data underscore ID, title, data, title.
We're also gonna have a side of bottom this time instead of right. And we're going to have a side offset of 10. And let's go ahead and render a div inside which is going to render a hint element which is going to have a label of main menu with the side of bottom itself and side offset of 10. And the hint is going to wrap our button component which is gonna render our menu icon from Lucid React. So we have a lot of composable elements here.
Make sure you import menu from Lucid React and let's go ahead and give this button a size of icon and a variant of board. There we go. Let's try it out. Perfect. And we now have a main menu and we can copy our link.
Let's see if that works. That works just fine. Perfect. And we can also remove the board. So now I believe if we remove the board, it's just going to be in this weird empty state.
Later we can add some redirects to it. So let me just go back here. And I think I need to create a new board now. There we go. You can see how now when you create a new board you're immediately redirected here.
So you can rename it from here then. Perfect. And you can delete it from here. Everything is working fine. So now our redirects are also immediately working.
Great! So we've wrapped this up. The next thing we have to do is we have to create this participants block, which will load all active users which are currently in this board. Great, great job!