{
  "video_id": "ADJKbuayubE",
  "title": "Build a Real-Time Miro Clone With Nextjs, React, Tailwind (2024)",
  "url": "https://www.youtube.com/watch?v=ADJKbuayubE",
  "transcript": "Hey there! My name is Antonio and today we're diving into the world of real-time collaboration with a project built using the newest Next.js 14. As you can see, we have two browsers, side by side, both interacting with the same content. In real-time, no delays, no waiting. Let's jump right in and see exactly what we are going to build. Imagine a digital whiteboard where you can brainstorm, plan, and bring ideas to life with your team, no matter where you are. Today, we're creating a Miro clone, a real-time collaborative whiteboard. As you can see, I'm simulating a sticky note meeting. It's like being in an actual meeting room, but better. You can move ideas around, add new ones, and watch as everything updates instantly for everyone. This is about bringing teams closer, even when they're miles apart. Next up, let's delve into the creative aspects of our whiteboard. Here I'm demonstrating app wireframing. It's not just about sticking notes. It's about bringing your ideas to life visually. We can draw, layer, reposition elements. Everything you need for a dynamic planning session. Imagine sketching your next big app idea and having your team contribute in real time, refining and reshaping it as you go. This is where creativity meets collaboration. Let's not forget about drawing. In this part, you'll see me just having fun with the app. I'm doodling, playing with different colors, and trying out various tools. This isn't just about neat, organized work. It's also about letting loose and getting creative. With our app, you can draw whatever comes to mind, mess around with designs, and even brainstorm visually with your team. It's a great way to break the ice and get those creative juices flowing. When it comes to organizing your ideas, the move to back and bring to front features are super handy, especially for complex projects. In this part, I'm showing you how these features work in our app. It's all about controlling the order of elements on your board. You can easily send a drawing to the background or bring a sticky note to the forefront. This is great for when you're working on detailed diagrams or need to prioritize certain elements in your design. It's a simple yet powerful way to manage your workspace and keep your ideas both visually tidy and well organized. Lastly, let's talk organization and collaboration. I'm demonstrating our favoriting system, creating organizations, and inviting team members. It's about making this space yours, tailoring it to fit your team's workflow. You can set up different boards, invite collaborators, and manage everything seamlessly. It's your workspace just the way you want it. Building on what we've just explored, let's talk about the tech that powers our app. We will be using Next.js 14, React, Tailwind, ShadCNUI, Convex, LiveBlocks, and Clerk. These aren't just buzzwords. They are what make this app incredibly responsive and real-time. Every single thing in this app, from drawing on the whiteboard to performing CRUD operations, happens live. Whether you're adding a new board, updating the title, or deleting something you no longer need, it all synchronizes in real time across all users. This means no refreshing, no delays, just smooth continuous collaboration. And now, without further ado, let's get started. Everything that you just saw in the demo is going to be covered in this free tutorial. That being said, if 10 hours of content is still not enough for you and you want to learn more, take a look at the link in the description where you can unlock additional content, where we are going to turn this project into a software as a service by implementing Stripe, adding a pro subscription, and a billing portal. So let's get started and let's set up Next.js inside of our project. So first of all, confirm that you have the necessary system requirements. You can do that quite easily by running this command in the terminal and seeing what it prints out for your current version. Next, let's go ahead and run the automatic installation right here. So I'm going to go inside of my terminal and I'm going to paste that command. Let's go ahead and give our project a name. So for me, this is going to be a board video tutorial. For you, it can be whatever you want. For the option of TypeScript, make sure you select yes, because we are going to be using TypeScript throughout this tutorial. Same thing for the S-Lint, same thing for Tailwind CSS. And for the source directory, make sure you select no, because we are not going to be using the source directory inside of this project. And the most important of all, make sure that you select yes for the app router options because we are going to be using React server components and the new app router inside of this tutorial. You can customize the import alias if you know what you're doing. Otherwise, you can just leave it exactly as it is. The add sign suggests select no. And now let's go ahead and install all of these packages. After the packages have been installed, go ahead and open the repository, which you can see right here. So I'm going to go ahead and click open and I'm going to select the new folder which was just created. If you get this prompt, you can feel free to click this, yes, I trust the authors. Inside of here, you should have the app folder with favicon, globals, layout and page.tsx inside. You should have pre-installed node modules. You should have some config files like Tailwind, TSConfig, PostCSS, and NextConfig. Before we run this project, I want to add our component library inside. For that, we're going to be using ShadCNUI. But ShadCNUI is not a component library per se. It is a collection of reusable components that we can copy and paste inside of our application, which is perfect for us because we are not going to need all of these components here. Instead, we're only going to need a couple of them and we're going to have to modify them to fit our design. So let's go ahead and go inside of the installation here and let's click on the Next.js. And as you can see, we already have the Next.js project, so we don't have to run this command right here. Instead, we can just run the command line interface inside of our new project. So go ahead and copy this command or you can simply write out what you see on my screen. make sure you're inside of your new project go inside of the terminal here and let's go ahead and do npx chat cn-ui at latest init like this go ahead and select the style which you want to use I recommend that you choose the default style because then you're going to have lucid icons installed as a dependency if you select the new york style you can still work throughout the project with that, but you are going to have to use different imports for the icons, which I will be using in this tutorial. So I recommend that you do the same thing that I'm doing and select the default style. And for the color, it really doesn't matter. I'm going to select the slate as the base color. And for the CSS variables, select yes. And that should automatically detect that we also have the TypeScript inside of our project and completely set up ShadCN UI for us. Great, and now we are ready to do npm run dev, which should start our application on localhost 3000. So I'm going to go ahead and refresh here, and you should see something similar to this. There we go. Perfect. Now let's go ahead and let's clean this up, and let's add a first component from this ShadCN package, so we can actually see how exactly we're going to work with components and how we're going to build our own component library. So the first thing I want to do is I want to go and find this page right here where we have the text Next.js. Inside of Next.js, there is something called file and folder based routing. So if you want to find this root page, you have to look for the file named page. Since we don't have any other routes inside of this project, because we just initialized it, we know that it has to be this file right here. So if you're coming from single page application or react world, this would be the equivalent of an index inside of a router. Go inside of this page.tsx right here, you can select everything which is inside of this return function and simply remove it like this. So we have a clean slate to work with. And let's go ahead and just add a little paragraph here. hello board and let's go ahead and remove the import image since we no longer need that and once you save this you can refresh and you should see hello board right here perfect so what I want to do now is I want to add a component from chat cnui we can do that by going inside of the components here and let's find the button for example in here we can see how it's going to look like. If you chose the New York style, you can see that it looks slightly different, but I'm working with a default style. And now we can choose how do we want to install it. We can do it manually by installing the Redix and then by adding this inside of our project, or much more simpler, we can just run the CLI command like this. So let's go ahead inside of our project here. Let's go inside of the terminal. You can shut down the app or you can open a new and go ahead and run npx chat cnui at latest add button and this will add the new component inside of your project but not in the way that you're used to so usually when we install component libraries all we have is a node modules file right and then we import from that package that is not the case with chat cnui instead this has now been added to our components folder ui button.psx and In here, you can see the exact code which is used for this button. And you can see how it has all the types we need. It has the variants predefined. It uses Tailwind, everything that we need. And it's also, of course, accessible. Everything which you would want in a component library button. So how do we import it? Go inside of page.tsx and let's go ahead and remove this. And let's add the button from add slash components UI button. and I'm gonna write click me. Now let's go inside of localhost 3000 and see that button and there we go, it's right here. And you can play around with it and you can take a look at the source code and you can see exactly what you can change So we have the variant and we have the size So if you try and give it a size of small you can see how it gets smaller. If you go ahead and give it the variant of destructive, you can see how it becomes destructive. And here's the cool thing. Since we have the source code available, nothing is stopping us from actually adding our own variant or adding our own size or modifying the class names for already existing variants. For example, I can add success here. I can give it a text primary and bg green 500. And then I can go inside of page.tsx and change the variant to be success. And you can see how that changed the button right here. So that's how we're going to use ShadCN library to add our own components and to slowly modify them and style them according to our design. And you can see how the moment I removed that success variant from the source code of the button, it also throws an error here. Great. So this was a small introduction into our framework, Next.js, and also our component library, ShadCN UI. In the next chapter, we're going to do a small demonstration of how routing works inside of Next.js. And then we're going to start by adding authentication to our project. So now let's go ahead and explain the project structure a bit more. And let's talk about the routing inside of Next.js. So first things first, it's quite important to understand the top level folders which are available, which is the app folder, pages folder, public folder, and the source folder. In our project, we only have the app folder and the public folder. So we don't have the source folder because once we initialize the application, I told you to select no for the source option. So all that we do is be additional or optional application source folder, but I have no preference around that. So I don't use it inside of my projects. The next one is the app folder. The app folder is where we're going to have all of our routing system, our React server components, our interactive components, basically everything that needs to be rendered under a specific route or a layout. Whereas this components folder is our custom named folder. You can see that that is not reserved. It's not specifically recognized in Next.js as something special. This is our convention, which was made by ChatCNUI once we added the button using the npx chat cnuiaad button command. Great. And the pages folder are the old way of routing in Next.js. So if you ever used Next.js in the past, you've probably used the pages folder. That has changed and today the default is the app folder. That being said, if for any reason you still want to, you can always create the pages folder on the same level as app folder and then work as you are used to, but keep in mind that you're not going to have server components inside of that. Great, so we covered top-level folders. Now let's talk about top-level files. So we only have a few of those here. We have nextconfig.js right here, which you can see is completely empty for us. In here, you can, for example, extend Webpack if you need that. Then we have package.json, which you're probably familiar with. In here, you can see all of our scripts and dependencies which we have installed inside of our project. We're also going to have a middleware file, but we're going to do that later. Middleware can be used to intercept every single route and decide whether to block a user from showing that route, so it can be used for authentication, or it can also be used to detect bots and IP addresses and block someone from your website. Then we have your usual .environment files. We're also going to be using those. Slin configuration, git ignore, basically all of the configuration files and you can read more about what they mean here. And now let's talk about the routing conventions. So how did I know that in order to change this page I needed to go inside of the app folder and specifically go inside of page.tsx? That's because I know the app router routing conventions. So I know that every file which is named page and ends in any of these extensions right here represents the page. And I also know that every layout represents the layout. And you can see all different reserved file names inside of the app router. So layout, page, loading, not found, error, global error, route, template, and default. Using this project, we are only going to be working with the layout and page and perhaps loading and error. we're not going to be using the other ones. And the route is simply used if you want to create API endpoints. And we are actually going to have one route, my apologies. Right, so how do we actually create a new route, right? We know that it's page.tsx, but where do we put that? Well, here's an example of nested routes, right? If you create a new folder, and then inside put a convention page.tsx, that folder is going to become a new route. So let's try that out. inside of the app folder, I'm going to create a new one called test and then inside a new file page.tsx. And here's what's important every time you create a page.tsx. It needs to export default. So make sure that when you write something like page and return a div saying hello test page, you need to make sure that you write export default page. Otherwise, it's not going to be registered as a route. So this is my structure. Inside of the app folder, I have a new folder called test and inside of it page.psx. What I can do now is go to localhost 3000 slash test and there we go. We have hello test page and you can try and comment out the export default and you can see how it's immediately going to break the page and give us an error. Great. So we know how to create a very simple route now. And here's an example of how to create a nested route. So let's try that out. Inside of the test folder, create a new folder called subroute. And inside of it, let's go ahead and create a new file page.tsx. And let's do the exact same thing. Const page. And let's return a div subroute page. And let's export default page. And you've probably noticed the name of the component you are exporting does not matter. It can be named whatever you want. All that matters is that you do a default export. What does matter is the file name and the folder name. The folder name can be whatever you want, but keep in mind that that's going to be the exact URL inside of your application. So make sure that inside of the test folder you have another folder, whatever you called it and did a default export. And then I'm going to go on localhost 3000 slash test slash sub route. And there we go. You can see that now I'm on the sub route page. Great. And in here we have some additional types of folders and routes like dynamic route segment. This will represent something that is not hard coded, for example, a user ID. Let's check that out. So I'm going to go ahead inside of the app folder and I'm going to create a new one. Inside of curly brackets, I'm going to write user ID like that. And perhaps it's better that we actually create users first and then let's drag and drop user ID inside of the users like that. So make sure you have app folder, users, user ID, and then inside let's create a page.dsx right here. Now in here, we're going to have something special. We're going to have an interface, user ID page. It doesn't matter. It can be named whatever you want. And in here we have access to the params. And I know that inside I'm going to have a user ID, which is a type of string. So how do I know that I have user ID here? Because that's how I named the variable inside of my square brackets. That's why naming is important. And now what you can do is write const page. let's go ahead and extract the params and let's assign the types here user id page props and in here let's just not misspell this let's return a div user id and let's write params dot user id like that so let's take a look at our routes inside of the app folder i have users and then I have user ID and inside of it, I have page.tsx. What I can do now is go to localhost 3000 slash users slash 1234 and let's go ahead and export default. So there we go. I forgot to do that. Always make sure that whenever you are creating a new route, you add a default export. I forgot to do that. My apologies. And there we go. You can see that now we have a user ID, which exactly matches what we have inside of our URL. Perfect. And you can also do a catch all route segment or optional catch all route segment. And now here's an important thing that you also have to understand. Folders are not always necessarily, don't always necessarily have to be used for routing, right? Sometimes we just want to create a folder to organize something. And we can do that using this convention by adding parentheses. This will group routes without affecting the routing. But that is different from adding an underscore. This will completely opt folder and all child segments out of routing. So what is the difference between those two? Let's go ahead and quickly explain. So let's go ahead and create two examples in here. So inside of my app folder right now, if I want to access the sub route, I have to go through test like this. So I have to write slash test slash sub route. that is the only way I can access this. But here's an alternative way you can do that. So I'm going to remove the test folder entirely and all of its children, and now this will be a 404 page. What I can do is I can wrap test inside of parentheses like this and then create a new folder sub route. And then inside create page.psx, let's create a const page, return, and I'm going to write rendered at slash sub route. And let's not forget to add expert default page. And how do you think we access this sub route now? Let's take a look at what this says. If something is wrapped inside of parentheses, group routes without affecting the routing. But that doesn mean that the children are not affected So all that we did here is we changed this parent folder to not be used as a part of the URL Instead it just a useful folder if we want to organize some things together Because when it comes to file and folder-based routing, when it comes to a very big project, it can get quite haptic. So now what we can do is simply go to localhost3000 slash subroute. And there we go. Rendered at slash subroute. because we are completely omitting this parent folder right here. And what is the difference between this one, you might ask? Well, that one is quite simple. That will completely remove everything. So I'm going to remove this folder and I'm going to create a new one, underscore test. And then inside, I'm going to create a normal route like this, page.tsx. Let's go ahead and export page. div i am never rendered so this is my folder structure app folder underscore test sub route page dot tsx very similar to the last two examples we had but this time the parent route has an underscore so let's see what happens if i refresh here slash sub route is a 404 how about slash test slash sub route. That is a 404 as well. How about slash underscore test slash sub route? That is a 404 as well. So whenever you see me using the underscore, which I will most likely use for some internal components, which are not reused throughout the project, it means that I am purposely doing that. So that is, in my opinion, the best way to hold components inside of the app folder. Because otherwise, let's say that you have a components folder inside of here, and you accidentally create a page component like this, for example, like this, you know what you mean by this. You don't expect this to be a page, right? But since this is a default export, so I just copied and pasted from here, if you're confused. Inside of my components here, I've pasted this, and if I go to localhost 3000 slash components now, this is all of a sudden a file route. Well, that's not what we expected. Most likely if we have a folder named components, that's why I recommend that you hold your components in underscore components. And you can see how that immediately turned this into a 404 page. And you can still import from this folder in all your other routes. So it's not like they don't exist. They're just omitted from the actual routing system. And every now and then, if you play around with this existing routes and change one from an underscore, then to a route group with parentheses, then to a dynamic one, you're often going to see this one unsaved file pop up, which is located inside of this .next cache. That is very simple to fix. You can either go directly inside and click save. But if you think you've messed it up for any reason, you can very easily resolve it. So first, go ahead inside of your terminal and shut down your app and then go ahead and remove .next entirely like that and here's where the magic happens all you have to do is npm run dev and that is automatically going to generate a new .next folder so don't worry you cannot mess that up it can happen when during you know hot reloading and actual development you change the type of your folders it probably confuses next.js a little bit So that cache happens. Great. And just one more thing that I want to cover is the layout file. Because yes, we have some more like parallel and intercepted routes. I'm not going to talk about them in this tutorial. I'm going to reserve that for something else. But I do want to talk about layout file because that's another file that I'm going to be using inside of this project. So that one is quite simple. Let's take a look at an example that we have. So inside of this users folder, right now I can go to slash users. slash one, two, three, four, five, six, right? There we go. So that is working normally. So now let's go ahead and inside of this users folder, let's add a separate page.tsx. So now both my users folder has its own page, but also user ID has its own page. And in here, I'm very simply going to export a page saying div user list. So now I can go to localhost 3000 slash users. And there we go. now I have a user list. So how does layout work in here? How are layouts useful? Well, let's say that I want to have a specific navbar whenever a user is visiting the entire user's route group. I don't care if they are in the specific user ID page or on the user list or even down further if I create more sub routes like specific user settings. I always want them to have a specific navbar. Well layout is a perfect file to put that so we don't have to copy and paste that navbar component every single time. So inside of your most top level route group or I mean you will know where you want that of course but this is just for an example. Inside of the users here alongside page.tsx we're going to create a layout.tsx file and this one will immediately going to throw you an error because we need to export default the same way we do with the page folder. So let's go ahead and write const layout. Again, the name doesn't matter here. And let's just return I am a layout. Ignore that typo, of course, and let's export default layout. And there we go. Now it says I am a layout, but where is our content? Where is the user list? And if I go to slash user slash one, two, three, where is the user ID? We cannot see either of those. Well, that's because layouts work a bit differently. Every time you use a layout, you need to export the children. So let's create an interface, layout props here, children, react, react node. And in here, all you have to do is assign those layout props and you can extract the children. And then inside of this div, simply render the children. And there we go. Now, our pages work exactly as they used to work before. so I can see the user list on slash users and I can see the exact user ID here. Perfect. And here's where the magic happens. Go inside of your layout.dsx. Let's go ahead and give this a class name of flex, flex call and gap y four. So we are going to order these elements one below another with a gap of four between them, which is 16 pixels. And in here I'm going to add a little navbar element, which is going to say I am a reusable navbar. And in here, I'm going to give it a class name of text extra small padding of one bg red 500 and text white. I think that is more than enough. And here's the cool thing. This layout is reflecting every single page inside of users. So currently I'm on user ID, but if I go back to slash users, you can see that this is right here. And here's the cool thing. This is not re-rendering. So that's why these layout files are so powerful. Whenever you want to create a sidebar or a navbar, layout is the perfect place to put that because then all of the other content, which is changed by your routing, will only be rendered inside of this part right here. and the actual layout like the sidebar or the navbar are never going to be touched except if there is a refresh in a server component inside of themselves but we're going to talk about server components when we encounter them first great I hope this kind of cleared it up and here's what I'm going to do just to wrap this up I'm going to delete everything here just because I want you to be on a blank slate here so we don't need the components the test or the users like that so we just need page.tsx and go back to localhost 3000 and you should have the click me button and just to explain this layout is different from the one that I just told you because this is a root layout you need to have this file you can see that this one is quite important because it renders the html and the body but other than that it functions exactly the same as our layout from the users. Great, great job. Now let's go ahead and let's set up our database and our authentication to our project. First things first, I want to set up Convex, which is going to be our backend and database for this project. So head to convex.dev or use the link in the description and create an account. After you've successfully logged in, once you go to the dashboard, you should see a prompt similar to this. As you can see, I already have a project right here, which I've used to develop this app initially, but you're probably going to have an empty screen and an option to create a project like this. If you want to, you can create a project from here, but instead I'm going to go ahead and follow the documentation from Convex for Next.js quick start. So let's go ahead and do that. As you can see, we already have our next app, so we don't have to run this command. Instead, we need to make sure that we are inside of our project and then run npm install convex. So let's go ahead and do that. Make sure you are inside of your project here. And I recommend that you actually shut down your localhost and then just run npm install convex like this. After convex has been installed, you're going to go ahead and run the next command, which will set up convex dev deployment. And that command is npx convex dev. So let's go ahead and run that. So what this command is going to do, as you can see right here, this will prompt you to log in with GitHub, create a project and save your production and deployment URLs. It will also create a convex folder for us to write backend API functions. So let's go ahead and run npx convex dev. So I am already logged in, so it didn't prompt me to sign in with GitHub. But if this is your first time running this command, it will give you a login link, which you can click, which will simply synchronize your new account, which you just created from Convex with your terminal here. So I'm going to choose a new project here because I didn't create one from the UI. So new project. And let's go ahead and give this project a name. As you can see, it already picked up the name of my repository. So I'm actually going to call it like that. Board video tutorial. Or I could have just pressed enter. but it doesn't matter. Just give your new project a name. And there we go. Now it's creating a new project right here. And as you can see, it added some fields to our environment variables right here. And now it says convex functions are ready. So this command npx convex dev, besides being used to create and initialize a project is also used to run our backend right So this is like a separate backend which we have but the cool thing is that we can write it in a monorepo right here We don need another repository for that So make sure that you have this running and then open a new project here, sorry, a new terminal, and in here do npm run dev. So you're gonna need two commands to run this project. One to run your backend, which is npx convex dev, and one to run your frontend, which is npm run dev. Keep in mind that the project will work also if you don't run npx convex dev, but every time you add some changes to your database schema or add some new API functions, you need to run this command again. Or you can simply leave it running in the background and that will synchronize everything with the convex, which is really a great experience. you're going to see that once we start developing, especially if you see my old projects where I use Prisma a lot, where you know that every time I change the schema, I need to do the whole migration process and DB push and NPX generate. Well, here, we don't have to do that. It's all automatically run and it's so fast to work with. Great. So now once we've done this, let's go ahead and refresh our localhost to make sure that this is still working as it should. Great. And now let's go ahead inside of our team here. And there we go, you can see that I have a new project here, board video tutorial. And you can see that it's completely empty. And we also have instructions on how to create our first mutation to create a task, for example. And we're going to spend a lot of time in this dashboard right here. So in here, we can see our data and our tables, just like in NPX Prisma Studio, for example. But the cool thing is we also have logs. So you're going to see that once we create some API routes in Convex, logging errors or some info is as simple as adding console log to our API function, which sounds like a trivial thing, but it's actually so impressive how well it works and how much it helps you debug in development and also in production later on. And not to mention that this entire thing is real time, which will give our app an amazing optimistic feel. Great. So now that we've set up our backend, it's time to set up clerk and connect it with convex using a JVT template. So we can actually add a login screen here. So the next thing we have to do is we have to go inside of clerk.com and go ahead and create an account or sign in. Once you're signed in, go ahead and create a new application. So in here, I'm going to give this application a name of Bordi. So you can see how that's going to look like. It's going to say right here, continue to Bordi and make sure that you select email address. This is very important because if you don't allow email addresses, you're not going to be able to add invites for organizations, which also come from clerk. And besides email addresses, I also like to allow Google. So it doesn't matter what else you allow here. You can see you have a ton of options. What matters is that you also include email address. And then go ahead and click create an application. And then inside of here, let's go ahead and let's set up our project. So let's go ahead and copy this right here. And let's create our dot environment dot local file inside of the project. So let me go ahead and expand all of this. And I'm going to create a new file dot environment.local. Oh, my apologies, we already have that environment.local because Convex has generated one for us. As you can see, this is my Convex URL and this is my Convex deployment. Great. So just below that, go ahead and in your existing environment.local, simply add the next public clerk publishable key and clerk secret key, which you can find right here after you've successfully created your project. And then go ahead and click continue in docs and just make sure that next.js is selected right here. Great. So let's go ahead now and let's install this package npm install clerk next.js. So for that, I'm going to go inside of my terminal here. Let me just expand my screen as much as I can. And if you want to, you can shut down both terminals while you're doing this. So let's just do npm install clerk next.js here. And then let's go ahead and see what else we have to do after we've done that. So we've already added our environment keys, so we can skip step two. And then what we have to do is we have to wrap our application inside of a clerk provider. But the way we are going to actually do this is by creating a universal provider with both convex and clerk. So how do I know that that's how we need to do that? Well, that's because I have the convex clerk documentation right here, which shows us these exact steps, you know, sign up to clerk, create an application. But then we also need to create a JVT template. And then you're going to see right here that we need to create a convex provider with clerk, which actually uses that clerk provider, which we are seeing right here. So I'm going to skip this step for now. And instead, I'm going to go immediately and create a middleware file. So our app becomes protected. So let's go ahead and copy this from the middleware part. So step four, require authentication to access your app. And let's go ahead and create a new file here. I'm going to call it middleware.ts. And remember, middleware is part of reserved file names inside of Next.js. So please don't misspell this. Otherwise, it's not going to be registered as the middleware. and simply paste this inside. What this is going to do is it's going to protect every single page inside of your app. So right now, if you started the app, I believe we will get an error because we didn't wrap our app inside of clerk provider. But don't worry, we're gonna do that in a moment. So just make sure that you have your middleware set up here so your app is protected. And that's actually it for this part of clerk. We can close this for now. and instead what I want to do is I want to go back inside of my clerk dashboard and create a JVT template. So let's go ahead right here inside of our clerk dashboard and in the sidebar here you can find JVT templates. So go ahead and click on new template here and here we have the option for convex. So select convex and go ahead and give it a name of convex like this and in here all that matters is that AUD is also named convex. And you can see that right here, when we have to add our outconvig, outconfig for convex, we're going to have to read the AUD field. And that's going to be our application ID for our configuration. So just make sure that AUD is convex right here. Perfect. And you basically don't have to change anything here for now. Later, we're going to extend our JVT template here with some other information. You can see that we can add a bunch of things. So whenever we destructure our user, we have some more information like the organization and stuff. But for now, you can just click apply changes and make sure that you have this convex right here. So it's not automatically created once you click new template, you need to click apply changes. And then you're going to have convex template generated right here. Great. So now let's go ahead and see what it says it says copy the issuer url from the issuer input field but before we do that let's actually copy this from convex out config js and you can see where that is in the convex docs authentication clerk right here i'm also going to leave the link in the description but it's also a very short piece of code so you can just code along with me so let's create inside the convex folder a new file out.config.js let's go ahead and quickly do that before we wrap our application in the provider. So inside of convex, let's create a new file out.config.js. And inside, I'm simply going to paste this right here. And you can ignore the warnings here. And what we have to do now is we have to change the domain to be our actual issuer. And the application ID can change, it can stay the same. Well, you will know whether it can stay the same or not. So the application ID needs to match the AUD claims field. So go back inside of your JVT templates here inside of your newly generated convex and confirm that the AUD is convex. Great. And then in here you have the issuer field. So just copy the issuer like that and replace this empty, this dummy string where it should hold the domain with your account like this. perfect. And now that we've done this, to ensure that this auth.config.js is correct, and that you don't have any typos or misspellings here, what you can do is go inside of your terminal, and you can attempt to run npx convex dev. And if it is successful, it will just tell you that functions are ready. There we go, convex functions are ready. But I believe that if you try and add something that doesn't exist like this, there we go, you can see how that will throw you an error because you have an invalid out.config.js. So just make sure that this is application ID and that this is a domain. And there we go. If everything is okay, you're just going to see convex functions already. Perfect. So I'm going to shut this down for now. And now let's go ahead and let's create our provider, which we can find right here. I believe it is in step five. Sorry, step eight right here. So configure convex provider with clerk, but ours is going to be a little bit different. So we're going to use a mix and match of the instructions from the documentation from Clark.js and the one right here. But we actually don't need documentation for that. You can just follow along exactly what I'm doing. So let's go ahead and create a new folder in the root of our application called providers. And then inside, let's go ahead and create a new file convex-client-provider.tsx. Let's go ahead and mark this as use client. And let's go ahead and import clerk provider. And let's import use out from at clerk slash next js. Now let's go ahead and import convex provider with clerk from convex slash react clerk like that. And now let's go ahead and let's import the following from convex slash react. That's going to be out loading authenticated. And we're also going to have convex react client. Like that. And now let's create an interface, convex client provider props, which is going to have the children, which are a type of react dot react. No, so this is going to be a wrapper around our end.\nentire application. So it kind of works as a layout, right? But it's not exactly a layout. This is just a provider, which will protect all of our children, which will be all of our app with authentication. So now let's go ahead and let's get the convex URL to be process.environment.next underscore public underscore convex underscore URL. And you can put an exclamation point at the end here like that. So it's never undefined. Great. And you can just confirm this by going inside of your dot environment dot local and confirm that you didn't misspell it. So next underscore public underscore convex underscore URL like this. Great. And now let's add a convex instance. So that's going to be new convex react client and passing the convex URL like that. And if you don't put the exclamation point, you can see that then we have a little error here. So you can just simply put an exclamation point here because we know that we have this inside of our environment local. Great. And now let's export const convex client provider. Convex client provider like that. And in here, what we're going to do is we're not going to destructure the children. And let's also assign the types of convex client provider props here like that. And then we're going to return a clerk provider at the top, then inside convex provider with clerk, and to it, we're going to pass use out to be use out, and we're going to pass in the client to be convex, and what we have to do inside is simply render the children for now, like this. There we go, so you can see that we don't have outloading and authenticated being used, but we're going to use that in a moment. What I want to confirm now is that once I add this to my root layout page, my app will become protected with authentication. And once I start my localhost 3000, I should be immediately redirected to a common login page from clerk. So let's go ahead and do the following. I'm going to go ahead and run npx convex dev in one part of my terminal. And in another, I'm going to run npm run dev. And now let's just go ahead and go inside of our app folder layout.tsx right here. And let's go ahead and wrap everything inside of body with our new provider. So in here, let's add convex client provider. and let's wrap the children inside of it. And let me just show you where I imported this. So right here, import convex client provider from at slash providers, convex client provider. So that's because it's located right here in the providers folder, which is in the root of my application, not inside of the app folder like that. Great. And once I save this, and if I go ahead and visit localhost 3000 now, I should be redirected to, there we go, my issuer URL, you can see how my URL changed, because we are using a JVT template. So we can use our issuer URL, because we've used it right here in the convex out config right here. So we successfully set up our authentication with clerk and convex at the same time. So now let's go ahead and let's log in and see what happens. So now once I'm logged in, you can see that I'm redirected back to localhost. And there we go. Now I have full access to my application. Great. So what I want to do now is I want to add a little loading state, because there needs to be a kind of a synchronization between convex and clerk to decide whether you are actually logged in or not. And we also need to add a way to log out, right? So let's go ahead and do that. so in order to create a nice little loading screen before convex and clerk decide whether you are logged in or not we need to find a logo for our application so you can use any image you want i highly recommend that you visit logo ipson.com they have amazing placeholder logos just make sure you don't use it for you know your actual business so they are completely free and open source. I'm not sure if they're open source, but you can use them for your demo apps, right? So I have one downloaded inside of my GitHub repository. So you can just use the link in the description and find my public folder. And in here, you're going to find logo.svg. So this is straight from logo Ipsum. You can visit that website to find even more logos. So just download this picture or find any other logo you want to use for your application and go inside of the public folder and simply drag and drop that inside. So if you remember from our first module, you learned that public is the folder where we're going to keep our static assets. So logo is the perfect place to put that folder in. Just make sure you rename it to logo.svg. Make sure it doesn't have any numbers besides that. Great. So what we're going to do now is we're going to create our loading component, right? So let's go ahead inside of our components folder here. And in here, I'm going to create a new folder called out simply to separate that from any other loading component, which we might have. And then inside, create a loading.tsx. And let's import the image component from next slash image. And then let's go ahead and export const loading. and let's go ahead and return a div with a class name of full width full height flex flex call gap y4 justify center and items center so it's going to be a element which has full width, full height and uses flex to simply center our image. And we actually don't need a gap Y4 at all, because it's just going to be a single image inside. So let's render this image in a self closing tag. Let's give it the source of slash logo.svg. Let's give it an out of logo. and let's go ahead and give it a width of 120. Let's give it a height of 120 and let's give it a class name of animate pulse and duration 700. So it has kind of an indication that it is loading. So just confirm that this logo.svg matches what you've put in your public folder here, logo.svg. And then let's go ahead inside of our provider Convex client provider and now we can use outloading and render that instead of the children here. So let's go ahead and render the children only if we are authenticated like this and then below that if we are outloading then render the loading from components outloading. So make sure you have outloading and authenticated imported from convex react and make sure that you imported loading from add slash components outloading, which is the component we have just created. So let's see if we need to modify anything. I want to zoom out just a bit to see if my logo is centered or not. So it seems like my logo is not exactly in the center. So every time you refresh, you should be seeing this loading component here. If you are, of course, logged out, it will redirect you to the login page for the issuer, right? But we currently don't have a logout button. So it's kind of weird. We don't even know whether we are logged in or not, but we're going to fix that in a second. So we need to do something to actually center this. And I think I know exactly what the culprit is. So in here, we use the full height and full width. But I believe that inside of our app folder, globals.css, we are missing some base styles here to actually allow our app to use full height of our screen. So go inside of the app folder, globals.css here, and add HTML, body, and comma, colon, root, and add a height of a hundred percent like that and simply save that file and now when I refresh there we go you can see how every time I refresh there is kind of a loading screen for our out and I think that it just looks really nice especially with this pulsing effect right here perfect and this is of course only going to be when the user initially visits our website it's not going to be on every route change right so it's only when you first time load the website like this on a refresh. Great. And now what I want to do is I want to go inside of the app folder page.psx right here. Let's change this into a div saying this is a screen for authenticated users only. And then in here, I'm simply gonna make this one below another. So I'm gonna use flex call gap y4. So it really doesn't matter, right? We're gonna remove this either way. I just want to kind of make a nice screen here. In here, we're going to add a user button from clerk next JS like that. And we can remove this button now. And there we go. It says this is a screen for authenticated users only. And if I click here, I can see more information about my account. And I can also sign out completely. There we go. And if I go back to localhost 3000 manually, I'm redirected back here. So now I'm going to log in again. And there we go. I'm redirected back. You saw the little loading indicator. And I can now see my other account. And in here, you can also see that you have the entire settings page where you can change your email. You can connect another account. You can log out from active devices. You can delete your account. And here's a cool thing about Clerk. So if you go to clerk.com, you're going to find a bunch of customization options right here. For example, branding. You can actually add any logo you want. So right now, when I log out, my screen just says, you know, log in to continue to Bordy, which is my application name. But you can also add a logo. So I'm going to add our logo here. It needs to be smaller than that. Okay, basically, you can try and add a logo. I picked the wrong image now. And then that logo is actually going to appear on the login page. And here's a cool thing. Even though this is for pro users only, while you're in development, you can test out any pro feature. There we go. So it just says this is a premium feature and they are free to enable on development instances So you can just click close here And now you can see how in here it says secured by clerk But if you wondering how it looks like once you go pro you can try it out And there we go. You can see how now you have no clerk branding. If that's something you're interested in. So I really like when companies do that, when they show us the pro features without us actually needing to purchase the pro features. so we can decide whether it's worth it or not. Great. So we wrapped up authentication. We now have our app protected. We have a nice little authentication loading. And most importantly, what we achieved here is we connected our Convex, which is a real-time database, which has some custom API typings, which you're gonna see in a moment. And we've completely secured Convex backend and our Next.js application at the same time. So that's why we needed to do the whole JVT template and the issuer and all of those things. But all of that is going to become more clearer once we start actually building some queries inside of Convex. And you're going to see how we are going to have access to our currently logged in user using the identity there. Great, great job. So now that we have our authentication and database ready, I want to go ahead and create a basic layout for our application. which will include the sidebar right here to pick and choose different organizations, another sidebar here to pick between favorites or recent words, and a navbar right here. And then the rest is going to be content inside. So the first thing I want to do is I want to move my root page.tsx inside of a route group folder. So I don't want this page.tsx to just be randomly laying around inside of my app folder. And if you remember from the second chapter inside of this tutorial, we learned about the route groups. So this is what I'm going to do. I'm going to shut down my app because if you remember, when you modify existing routes, Next.js cache can get confused. So just make sure that you are no longer running your app. And then let's go ahead and do the following. You can remove page.tsx completely. And then inside of the app folder, create a new folder dashboard like that. And then inside create a page.tsx. And this is now gonna become our new root page.tsx. So this is the exact same thing as having page.tsx inside of the app folder. But this way we are organizing it better. So I know that this represents my dashboard. And what I have to do here is simply export the dashboard page. So I'm going to create a div dashboard root page like this. So just make sure you do export default and make sure it's right here. And then if you go ahead and do npm run dev here, you should not be seeing anything different, except it should simply say dashboard root page right here after it loads. There we go. Now let's go ahead and create a reusable layout file. for our dashboard organization sorry for our dashboard route group so inside of dashboard create layout dot dsx like that and this will now throw an error so we're going to fix that quickly by creating an interface dashboard layout props and we are very simply only going to have the children which are a type of react node as the props so let's go ahead and write const dashboard layout. And in here, very simply, let's extract the children and let's assign the dashboard layout props. And let's simply return the children like this. And then don't forget to export default dashboard layout like that. Perfect. So now you should no longer be having any errors and you should still see the dashboard, the root page, which we defined right here. Now let's go ahead and create our sidebar. So for that, we're gonna go inside of the layout.psx and we're gonna replace this wrapping div with a main element like that. And then we're gonna give this main element a class name of hFull. And then in here, I'm gonna render a sidebar component. And I'm gonna go ahead and wrap the children inside of a div. And all I'm gonna do for now is give the div, which is wrapping the children a padding left of 60 pixels. Like that. And let's also give it a full height. And of course, we have an error for the sidebar because it doesn't exist. So inside of the dashboard, I'm going to create an opted out folder, underscore components like that, because these components are only going to be used inside of the dashboard. So that's why I'm not creating it inside of here. So inside of this underscore components, I'm going to create another folder called sidebar. And let's go ahead and create an index.tsx inside of here. And very simply, let's just do const sidebar, actually export const sidebar. And let's go ahead and return a div saying sidebar. Now we can go back inside of the layout and we can use this underscore components sidebar to import the sidebar. So I'm going to go ahead and import sidebar from dot slash components slash sidebar like that. Perfect. And there we go. You can now see that we have a little sidebar here. So let's go ahead and style this sidebar just a bit. So go back inside of components sidebar index dot tss. And the reason I didn't just create a file named sidebar, why did I create a folder? Well, that's because sidebar is going to have multiple smaller components. So it's just easier to keep that all in a folder. And let's change this from a div to an aside element. And let's give it a class name of fixed. Z index of one. Left is going to be zero. Background is going to be blue 950, like that. Height is going to be full. Width is going to be 60 pixels. Flex, padding three, flex call, and gap Y4. like that. And then in here, this will represent our sidebar. So you can add a little plus, for example, or whatever side, something that you can see being rendered right here, you can also give it a text of white if you want to. There we go. So now you can see that this is our sidebar, right? You can see even when I expand, this is the sidebar. And this is now the dashboard root page. And even on mobile, it is still visible here, but that's how we want it. What we have to do now is we have to create another sidebar, which is going to be called organization sidebar, which is going to take this space right here. And then this will be pushed here instead. So let's go ahead and do that. So we have to go back inside of our main layout here. and inside of this wrapping div, let's create a new div with a class name of flex gap x3 and h full. And that is also going to wrap the children. So just make sure you add children inside of this new div right here. And then let's go ahead inside and wrap the children simply one more time with a div and a class name of h full and flex one and i'm going to add a comment here add navbar and then in here we're going to go ahead and render the organization sidebar like that so above this div which wraps the future navbar and the children so just the organization sidebar so let's go ahead and let's go inside of components again and for this one we can simply create a new file organization sidebar.tsx because it's going to be much simpler than this sidebar for which we need a folder so in here let's mark this as use client so we prepared this so use client means that this is going to be a client component and not a server component which means we are allowed to use hooks and use effects and on click and other interactivity elements. So let's just do export const organization sidebar here. And let's return a div. And now we're going to make it dynamic. So by default, it's going to be hidden. But on large devices is going to be flex. And when it's flex, it's going to use a column flex, space y six, width is going to be 206 pixels, Padding left is going to be five, and padding top is going to be five. So these are just some arbitrary values, like this width and this padding, right? I don't expect you to know how I got to this. I just did trial and error with the design until it looked good. Great. And then inside, I'm simply going to write org sidebar, like that. And now you can go back to the layout and import org sidebar from the same path. So .slash underscore components org sidebar, the same way we did with the original sidebar. And now it should not be visible, but when I expand, there we go. When you go to full desktop mode, you should see the main sidebar, and then you should see the organization sidebar. So just to make this easier, you can go inside of the organization sidebar and give this a BG Red 500. And now when you expand, there we go. it's much clearer what space this organization sidebar actually takes. Perfect. So what we have to do now is we have to create a navbar and then that is going to push this dashboard root page in this content right here. And then we are finally ready to start working on this sidebars and navbars and make them functional. And yes, on mobile devices, you should not be able to see the organization sidebar. So only on desktop. All right. So for now, you can leave this color if that is easier for you. We're going to remove that later. So what we have to do now is we have to create our navbar component. So let's go ahead and do that inside of this underscore components. So new file navbar.tsx. So the same way we did with the sidebar and organization sidebar. This is also going to be a client component, meaning that we're going to have to use some hooks here And let export const navbar inside of here And let go ahead and return a class name of flex item center gap x4 and padding of 5 And you can also give this a BG let use green 500 so we can easily see it And then you have to go back inside of the layout here. And where you have a comment add navbar, you can now simply render the actual navbar from the same place, the slash components navbar. And there we go. You can see how this looks on mobile. And let's also add a little text here. So navbar, like that. There we go. So you can see this is how it looks on mobile. And this is how it looks on desktop. And yes, there is a little space here. But that is completely OK, because the actual background color of the navbar and the organization sidebar is going to be transparent. So it doesn't matter that there is a little padding between them, because it actually looks better once we start adding some elements there. Perfect. So we have the basic layout. Now what I want to do is I want to actually put my user button to log out in the proper place, which is the navbar. So let's go ahead and simply render the user button from clerknext.js right here. There we go. Perfect. So now it is right here. And you can also do the following. So you can add a little div here. Let's do it like this. Let's add a little div. and I'm going to add to-do, add search, and let's give this a class name of hidden by default. And on large, it's going to be flex1 like this. And on large, it's also going to be flex by itself. And then in here, let's wrap the user button to be class name block by default LG. Actually, I think we can just leave it like this. And then when you expand, there we go. Yeah, my apologies. So I was looking on a mobile mode, so I was confused why it didn't work. So you can just add an empty div here where we're in the future, we're gonna hold our search input, which is hidden on mobile. And then when it hits desktop, it will become flex. and then it will also take up most of the space. So then it will nicely push the user button into this corner right here where you would usually expect your user settings to be. And if you want to make this clearer, you can add a BG yellow 500 just so you can see exactly when this appears. Or perhaps we need to give it some padding. I'm actually gonna write search. maybe that will make it a bit clearer there we go yes so this is going to be our search bar and that will push this button right here to the end this will be our organization sidebar from where we are going to choose either to look at the favorites or the recent boards and also to switch to switch organizations and to modify their settings to add invites or accept invites to other organizations this main sidebar right here will be used for a quick switch and creation of new organizations. And in here, we're actually going to render our boards. And then later, we're going to create another route group, which will just be used to render the whiteboard itself. Great. So you can see how this behaves on mobile. It's responsive nicely. And don't worry about this being here on mobile. That's going to change later when we add some more elements. Great, great job. What we have to do next is we have to modify this sidebar to actually allow us to add new organizations. Now let's go ahead and let's create the functionality so that when we click on a little plus button inside of this sidebar here, we open up a model to create our organization. And then we're also going to create a list so users can choose between all the organizations they have and quickly switch between one another. So the first thing I want to do is I want to go back inside of my clerk dashboard right here. So pick your application and go ahead inside of the organizations here on the side. And in here, you're going to have a prompt that organizations have not been enabled for this application. So go ahead and click enable organizations. And that's going to redirect you to organization settings. And just go ahead and tick this little box right here. There we go. And you can leave this exactly as it is. And now what I want you to do is go inside of JVT templates where you have the convex template. And in here, you now have access to stuff like organization ID, organization role and organization name and much more other stuff. So what I'm going to do is I'm going to go to the end and add a comma here. And I'm going to add organization role. And then what you can do is simply find the organization role here and click it like this. And there we go. You can see that now I no longer have any errors. If I for example, forget the comma, you can see that it has an error. So just make sure that you have a little comma here. And besides the organization role, let's also add the organization ID. And you can either write it manually, or you can click org.id right here. There we go. So just make sure that you don't have any errors. Make sure that you don't have trailing commas. So remove that as well. perfect so i just added the organization role and the organization id which is going to be very useful for us so we know which organization the user belongs to and also their role inside of the organization if you want to do even more granular control and just click apply changes right here and there we go if everything was successful you're going to get this template successfully updated and you should now see the organization id here and the organization role right here. Perfect. So now let's go ahead and what I want to do now is add a new component from chat CN, which is going to be called a dialogue component. So let's go ahead quickly inside of our terminal here. Also for this part, you don't need to have NPX convex dev running because we're not going to do any backend for now. So I'm just going to do NPX chat CN UI at latest add dialogue, which is going to add a new dialogue component. And besides that, I also want to install a tooltip component. So when we actually hover on buttons here, I want to show a little instruction on what the button is going to do. So let's add Shatzi and UI latest add tooltip as well. So we are adding a dialogue component and a tooltip component. And once those two have been installed, you can go ahead and run npm run dev. And every time that you shut down your app and then run npm run dev, just make sure that you refresh your page. Otherwise, your hot reload is not going to work until you refresh. Great. So now let's go ahead inside of our sidebar component. So I'm going to close everything here. Oh, yeah, we do need npx convex dev. My apologies. Otherwise, our authentication is not going to work. So let's go ahead and open a new terminal and run npx convex. Like that. Let's just wait. Convex functions are ready. And now let's refresh. There we go. Now we can work. And let's go ahead inside of the app folder, inside of components, sidebar. And inside of here, we are not going to render side. side. Instead, we're going to render a new button component, which currently does not exist. So inside of sidebar, create a new file, new dash button dot DSX. And inside of here, I'm going to mark this as use client, I'm going to go ahead and import plus from lucid react, just plus not plus circle, my apologies. I'm going to import create organization from clerk next to JS. And then I'm going to go ahead and import from components UI dialog, which we've just added a dialog, a dialog content, and a dialog trigger. So we have something to open it. Great. And now let's just export const new button here. And inside of here, I'm going to go ahead and return a dialog. Let me just invent this properly. And I'm going to go ahead and give this a dialog trigger, which we'll have as child crop. And inside of here, I'm going to add a div with a class name of aspect-square. And inside, I'm going to render a button, which is going to render our plus icon with a class name of text white. like that and let me just indent this a bit and now that we have this skeleton set up we can go back to the sidebar and import the new button so we can actually see this button that we are developing so even in mobile you should see a little plus icon here now which currently isn't doing anything so what i want to do now is style the button so it looks a bit better so let's give this button a class name of bg white slash 25 so it will automatically give it a bit of an opacity on the white color let's give it a full height and full width which is going to be the maximum of this aspect square ratio which is it is wrapped in and then let's give it a rounded of medium let's give it flex items center justify center like that there we go so now it already looks a lot better. And now what I want to do is give it a default opacity of 60. But when we hover on it, let's give it an opacity of 100. And let's make that smooth by adding a transition as well. And there we go. You can see that now when I hover on it, it pops up like this. Perfect. Now we have to add a dialogue content outside of the dialogue trigger here. And very simply render the create organization like this. And now if you try and click here, you should be seeing this create organization right here. But it looks a little bit weird. It looks like there are two divs, like two white backgrounds here. So we can resolve that quite easily, just by modifying our dialogue content a bit. So let's give it a class name of padding zero, BG transparent. Let's also give it a border none. And let's give it a max width or 480 pixels like this. There we go. So if I collapse this this is how it looks like Perfect So we are now ready to create our first organization Excellent And this is how this class name looks So max with 480 pixels border none transparent and padding of zero Perfect So before I create my new organization, first thing I want to do is create a list of organizations. So we can actually see them being rendered here. If you want to, you can already create one. You're going to see it in the clerk dashboard, but you're not going to see it here. So I just want to wait before we do that. So inside of sidebar, I'm going to go ahead and create a component called list.tsx. This list is going to iterate over all of our current organizations and then render them. So let's mark this as use client. Let's import use organization list from clerk next JS. Let's go ahead and well for now let's export const list by itself and in here before we return anything let's get the user memberships from use organization list and let's define user memberships to be infinite like that and then I'm gonna write if there are no user memberships dot length sorry dot data dot length. We're just going to return null. And inside of here, I'm going to open an unordered list and give it a class name of space Y4. And inside of here, I'm going to go over user memberships dot data dot map. And let's go ahead and get the individual membership. And inside of I'm just going to render a paragraph. And let's render the name. Membership.organization.name. And let's give this a key of membership.organization.id. Like that. There we go. And now I want to go ahead where we render the new button. And above it, I want to render my list from .slash list. so we have a list and new button great so now if i go ahead and create a new organization here i'm going to call this test and click create an organization and i'm not going to invite anyone so i can just press skip here and let me just close this and see if this has worked or not and there we go you can see how now we have test right here rendered and let's just confirm on our clerk dashboard that is also true so in your organizations right here you should see the new one there we go created today one member and it's called test so if i go ahead and create uh another one let's call this two and click create an organization i'm going to skip the invites like that let's go ahead and there we go. One, two, and in here when I refresh, there we go. Perfect. Now what I want to do is I want to display them in a better way. And I also want to indicate which organization is currently selected, which one is currently active. So for that, we're going to have to create a new component called item. So on the same level as the sidebar index and list, let's create a new one called item.tsx, which is going to be used client as well. And in here, let's import image from next slash image. And let's go ahead and import from at clerk next JS, use organization. And let's add use organization list. And let's also import CN from lib utils. We already have this. we got that when we installed chat CMUI. So in the root of your application, you have the lib folder and utils inside with the CN package, which we are going to use to dynamically style our Tailwind elements. Now let's create an interface item props and let's give it an ID of string. Let's give it a name of string and image URL of string as well. And let's export const item. and inside of here we can destructure the name and image url and id and let's just assign those props here so those are the item props and then in here i'm gonna return a div with a class name of aspect square so the same i did with my button so everything is consistent and let's give it a relative class name and then i'm going to render a image component and i'm going to give it a source of image url i'm going to give it an on click of an empty arrow function for now i'm going to give it an alt of name so let's order that like that and i'm also going to give it a property of fill and for the class name i'm going to go ahead uh and write the following so i'm going to make it dynamic by opening cn and for now i'm just going to give it some default classes which are going to be rounded md cursor pointer opacity 75 hover opacity 100 and transition so the way the cn library works is that in the first argument or any argument you pass in just the default classes, which you would pass it like you normally would in a class name. And then later we can do dynamic stuff. Like if something is true, we're going to do the opacity, you know, 100, for example, stuff like that. But we don't have anything to compare it with because we need to add some hooks here. So let's do the following. Let's go ahead and let's get the current organization from use organization. And let's go ahead and let's add set active to come from use organization list. And now let's check if this item is the active organization. So if is active, sorry, we are going to define is active to be the current organization, which we get from this hook question mark dot id is equal to the id which we are passing right here and then let's do const on click here if we are not at sorry if there is no set active we're just going to break the function so you can just actually return like that because it's possible that this set active is undefined right so no even point in calling this action if it's not undefined if it's not defined sorry otherwise we're going to set active and we're very simply going to pass in the active organization to be the current id of this item like that and then we can replace this empty on click to actually use the on click element and we can add dynamic class name here if is active very simply we're going to add opacity 100 there we go so now let's go ahead back inside of our list here. And now we're going to use that new item. So remove this paragraph here and instead use the item, which you can import from dot slash item. So all of those are on the same level inside of app folder, dashboard, underscore components and our sidebar folder, because all of those only related to the sidebar. And now we have to pass a couple of props here. So let's give it a key to be membership dot organization dot ID. Let's repeat the exact same property but for the id prop let's give it a name of membership.organization.name and last one is going to be image url and the prop for that is going to be image in url and there we go now you're going to get this error that the host name image.clerk.com is not configured inside of our next config so what we have to do is we have to go inside of our next.config.mjs right here and inside of here, extend this object with images, remote patterns, protocol is gonna be HTTPS and then host name is gonna be whatever is written for you here. So I believe it's gonna be exactly the same. So for me, you can see that it says that host name image.clerk.com is not configured. So that's the host name that I have to add right here, image.clerk.com. and once you save this I would actually recommend that you shut down your app and restart it because in previous versions of next this was never nicely updated so that's what I'm going to do so in your terminal where you run npm run dev just do it again and if you want to you can also restart npx convex dev but I don't think it's going to do any difference so let's try this again so I'm just going to go ahead and refresh my page let's do it a couple of times just to ensure this is working. And there we go. You can now see my organizations right here. You can see how when I select another one after a couple of seconds, that's the one that becomes with opacity 100. But we have two problems here. First one, there is no indicator what's the difference between these two. So one thing that we can do is we can use our tooltip component to add the name once we hover. But it would be nice if we could somehow generate the initial of the name of the organization to indicated the user which one is which. Well, clerk can do that. So you can go inside of clerk right here. And let's go inside of customization avatars right here. And in here, you have the default user avatar if you want to change the color or anything, but you also have the default organization logo. And in here, you can select the initials. And there we go, you can click apply changes. And now all of your organizations are going to have initials instead of those boards. If you want to, you can actually remove these organizations and then create new ones, or they're going to maybe be upgraded a bit later. Let's try this out with the name test. So if I create a new organization, sometimes it's not immediately reflected, but it definitely works because I use it in the initial project. There we go. So new ones now have this image. Yes. So you have to remove the old ones. You can do that by going inside of the organizations here and then remove the old ones. Yes. And also, I don't know why exactly when I finished creating an organization and when I click skip here, it seems like nothing is happening. So we're going to debug that later. But for now, you can just manually close. Okay. So what I want to do now is I I want to create a hint component so that when I hover over any of\nthese elements, I can see the full name of the organization. And also when I hover on this, I want a tooltip to tell me exactly what that button is going to do for me. So for that, we're going to go ahead and go inside of the components folder and create a new file called hint.tsx. And in here, I'm going to go ahead and import everything we need from add slash UI, sorry components UI tooltip. So we need the tooltip itself. We need the tooltip content. We need the tooltip provider. We need the tooltip trigger like that. And let's export interface hint props here. So that's going to take the label, which is a string. We're going to have children, which are react.react node. So we're going to have side, which is going to be an optional top, bottom, oops, so top, bottom, left, or right. We're going to have optional align, which is going to be start, center, or end. And we're going to have side offset, which is going to be an optional number, and align offset, which is going to be an optional number as well. and then let's go ahead and export constant hint and let's just destructure all of these props here so i'm going to align hint props and then we can get the label we can get the children side align side offset and align offset perfect and now inside of here i'm just going to return the tooltip provider and let's make sure we close this tag inside i'm going to add a full tip component itself with a delay duration sorry delay duration of 100 so it appears a bit faster than usually and let's add a full tip trigger component here which is very simply going to wrap our whatever we wrap it around and let's use as child to ensure the styles are not broken and there are no hydration errors either. And then let's add tooltip content here. And inside of the tooltip content, we are very simply going to render a paragraph, which is going to render the label. And inside of the paragraph, let's give it a class name of font, semi bold, and capitalize. And let's give the tooltip content some props. So class name is going to be text white, background black and border black and let's give this a side prop of side let's give it an align prop of a line and also side offset of side offset and align offset of align offset there we go and now what we can do is we can reuse this hint component wherever we need to reuse it. So the first place I want to do it is inside of the app folder components sidebar new button. So this button which is used to open the content. So I'm going to go ahead and inside of the div where we have the aspect square add a hint component from components hint and wrap the button around it. Whoops. Like that. So you can import hint from components slash hint, which we've just created. And let's give it a label of create organization. Like that. And says that hint cannot be used as a JSX component. Oh, I believe I forgot to write a return so yes my apologies wrap this entire thing in a return of course you can we cannot just write jsx plainly all right like that there we go no more errors and now if you try and hover over you can see that we have a label create organization the issue is is that it appears on the top that's not where i want it to appear so what we're going to do is we're going to give it a side of right. And then we're going to give it an align of start. And let's give it a side offset of 18. Like that. And there we go. Now it looks much better. Perfect. And now I want to use that in the item component as well so that we can display the name of each of these organizations. So let's go inside of sidebar item.dsx. Let's go ahead and import hint from components hint. And we're going to go ahead and rack the image in the hint component like that. And let's go ahead and give it a label of name. Let's give it a side right. let's give it an align of start and side offset of 18 like that there we go so now if i go ahead there we go you can see exactly uh which organization i'm going to click here perfect and you can see how the opacity changes once i click on a new organization perfect so we just did uh the entire functionality for our main sidebar right here so what we have to do now is we have to build the organization sidebar right here, which is gonna allow you to change the settings of an individual organization and to also switch organizations in a different view. Great, great job. So now I wanna go ahead and I wanna create this organization sidebar component right here. So in order to see that, you're either gonna have to be in desktop mode or if you are like me and you keep half of your screen with code, you can zoom out so you can see what you are developing. And also, I didn't forget about this little issue that once we create an organization and we click skip, nothing happens. But I am going to try and resolve that later in the tutorial. For now, I'm just going to focus on wrapping up our layout. I do definitely have a couple of ways we can fix that, but most of them include changing our layout a bit. I mean, when it comes to routing, right? But before we go into that, I just want to focus on wrapping up the organization sidebar, our navbar, and start creating some boards here. So let's go ahead and find where we have organization sidebar. So that is inside of dashboard underscore components organization dash sidebar dot dsx. So let me zoom out even more here so I can see both of them at the same time. And now let's go ahead and let's import link from next link. And let's also import image from next image. Let's also go ahead and import pop-ins from next font Google. And let's leave it like this for now. And now let's initialize the font using the pop-ins import from next font Google. So subsets for this are simply just going to be Latin. And weight is going to be 600. Like that. And what I want to do now is I want to display the text and the name of my application right here at the top. So I'm going to remove the BG color red. I no longer need that. We know how this is supposed to look like now. and inside instead of rendering org sidebar i'm going to render a link component which we added let's give it an href property of an empty string sorry an empty path like that and inside let's add a div with a class name of flex items center and gap x2 and then in here i want to render a image component and i want to give it a source of slash logo.svg. So basically the same thing that you use inside of the loading component, right, slash logo.svg, whatever you've used there. And let's give it an alt of logo. Let's give this a height of 60 and a width of 60 as well. So now we should have the logo of our app right here. and just besides that logo, I'm going to add a span element and I'm going to write the name of our application. So whatever you want it to be. And let's give this span a class name and let's add CN from lib utils. So just make sure that you add that so we can dynamically append this font class name here. So first let's write some default classes which are font semi bold and text to Excel. And then we can add font dot class name here. and that will change our font. There we go. Perfect. So now inside of our organization sidebar, I know it's zoomed out. So let me just expand a bit and zoom back in. There we go. So you can see how this looks like. Now this is our logo right here. Perfect. So what I want to do now is below it, I want to add an organization switcher so that we have another way of switching the active organization and also a better indicator of which one is active because this opacity by itself is just not exactly visible, right? So let me just zoom back out so I can see both of this for me. And outside of the link component here, we're going to add an organization switcher from clerknext.js. So just make sure you add organization switcher from clerknext.js. And that's going to be a self-closing element like that. And already you should be seeing something here. So let me zoom in. There we go. You can see how now we can switch between different organizations. And we can also create an organization from here. So what I want, and we can also trigger this manage organization as well like this. But what I want to do is first I want to hide my personal account. So we're not going to be working with personal accounts for this app. I only want organizations. So we can do that quite easily by adding a prop hide personal like that. So now when I expand here, you can see that, let me just refresh. There we go. You can see how it no longer has my name here. It only works with organizations. Great. And now let go ahead and give this a better appearance prop So go ahead and expand this object And let me just expand this so you can see clearly Let focus on the code for now So we going to target the elements We going to target the root box. We're going to give it a display property of flex. We're going to give it a justified content property of center. Align items property of center as well. And width of 100%. So yeah, I don't know if you knew that, but you can style clerk components, both the login, logout, all of those things, you can style them, they have the appearance prop, and you can target pretty much any element you want. So what I'm doing here is I'm just giving it some width and some you're going to see now some borders, just so it looks a bit better in this organization sidebar, because right now it looks fine. But I wanted to have a bit of a border, right? It just looks too plain uh staying here so let's go ahead and just add an organization uh which one is this organization switch switcher trigger component let's give it a padding of 60 sorry six pixels let's go ahead and give it a width of 100 percent a border radius of eight pixels and let's give it a border of one pixel solid and let's use e5 e7 eb color also i have this little extension here if you're wondering it's called color highlighting color highlight. So whenever you have a hex code, it will show you how that color looks like. I also have error lens, which shows the error in the same line. And I also use Tailwind CSS IntelliSense, which is quite important if you're working with Tailwind, it helps a lot. All right. And let's add justify content to be space between. And let's add background color to be white. Like that. So now if I show you, there we go, you can see how now it looks just a bit better. It looks like an actual button, right? Perfect. And now let's go ahead below it. So outside of the organization switcher, And let's open up a div and let's give this div a class name of space Y1 and full width. And inside, we're going to render a button from components UI button. And this one is very simply going to have a link to an href of a root slash and render a layout dashboard from Lucid React. So this is an icon layout dashboard from Lucid React. let's go ahead and give this icon a class name of h4 with four and margin right of two and i'm just going to write theme boards here i like that so let's go ahead and take a look at that there we go we have this team boards here so what we have to do now is give our button and as child prop Let's give it the size of LG. And let's give it a class name of font normal. Justify start padding on x-axis of 2 and full width like this. There we go. Now it's looking slightly better. So what I want to do now is I want to give it a variant of ghost. Like that. There we go. Perfect. and let's go ahead now and copy and paste this button and the one below it is gonna go to our favorite boards so we can write favorite and let's use a star icon from lucid react so make sure that you import layout dashboard and star from lucid react and then you can render that here let's take a look at this there we go we have team boards and we have favorite boards and we can already add a different link for this favorite boards the url is not going to be different the only thing that's going to be different is the query parameter so we're going to do the following we're going to go ahead and give this href to be an object which takes in the path name which is just an empty slash and query is simply going to be favorites true like that so favorites true also whenever you're writing favorites i see a lot of bugs happen to my viewers where they're inconsistent with writing sometimes they write favorites with a u and sometimes they write it without it so whenever you write this make sure that you are being consistent or you can create a constant and use it if it's easier for you it's just so you avoid bugs in the future because we are going to be looking for this query in the url to decide what to show to the user great so we have this now and now what I want to do is decide whether we are whether we which one should we render as the current active button right so if I click here there we go you can see how my URL has changed and added the favorites true here and if I go back it's removed so depending on that query I want to highlight a specific button to indicate to the user all right you are currently looking at this one. So let's go ahead and let's get our search params, which we can import from next. I think it's just next. It's next slash navigation. My apologies. So let's import use search params like that. And then in here, we can get the search params from use search params. And let's get the favorites from search params.get favorites. So again, make sure you don't misspell favorites here, here, or right here. Great. And now what we can do is we can dynamically change the variant depending on the favorites in the URL. So variant here, it's not just going to be hard-coded ghost. instead if we have favorites and this is where team boards that means that this one is going to be ghost otherwise it's going to be secondary so we can copy and paste this replace it with the lower button which is used to go to the favorite boards and that's going to be the opposite so if we are if we have favorites in our url query we're going to use secondary otherwise we're going to use ghost like that so let's check that out now so let me just go here and there we go you can see how now when I click on something it indicates that I am on it perfect if you want to you can change uh the secondary and the ghost I don't know if you want to use outline instead of ghost you can try out how that looks like let's take a look uh yeah in my opinion I think that uh ghost looks better. So I'm going to bring it back to Ghost. Great. So now we have a way to switch our organization. You can see how the opacity changes in the sidebar as well. Perfect. And from here, yes, we can also manage our entire organization and we can also do invitations. So let's just go ahead and quickly try out an invitation here. So the way I'm going to do that is I'm going to sign out and I'm going to go ahead and log in with a new account here. And now when I'm in this new account, I'm going to go ahead and see, we can create an organization from here now, even. So I'm going to create an invite test organization. I'm just going to click create an organization and already in here, we can pass in an invite to the member. So you can either do it immediately here or you can click in and go into the settings of the individual organization and I'm going to give a role of member to my other account. I believe this is my email and I will send the invitation and there we go invitation successfully sent. If you have any errors with this it's most likely because you didn't allow clerk to use emails so just make sure that you have inside of your user authentication, make sure you have email address enabled. If you don't have this, then you will not be able to invite your users. Great. So now I'm going to log out and go back into my other account. And there we go. You can see that now I have this little indicator one, and I am invited to the invite test organization and I can click join. And there we go. I can select that organization now. And that way we're going to be able to share all boards across the same organization. Amazing. So we just wrap that up. What we're going to do next is wrap up our navbar and then we're going to finally be working on this content right here. Great, great job. Now let's go ahead and let's modify our navbar. So it has some more features like an actual search bar and also looks a bit better on mobile mode where we are also going to have an organization switcher here. So let's go ahead and find our navbar component. We can find that inside of underscore components navbar.tsx right here. And let's remove this BG green 500 from here. We no longer need that. And let's remove BG yellow 500 from here as well. And first things first, let's go ahead and let's create the search input component right here so i'm gonna go ahead and do that inside of the underscore components here so search dash input dot the sx it's gonna be a client component and let's also export const search input here and let's return a deal search input like that. And now go back inside of the navbar. And instead of the text search, you can add search input here from dot slash search input. And right now, this will only be visible on desktop. So make sure that you are expanded or zoomed out enough so that you can see the search input here All right So now before we continue developing the search input I want to install two packages So let go ahead and let install query string And let's install usehooks-ts. None of these are strictly required. They are just faster for me to use inside of this tutorial. You can, of course, use native URL search barams, or you can build your own hook if you want to. Great. So let's go inside of the search input here and let's import everything we need. So we need QS from query string. We need search from lucid react. We need use the bounce from use hooks dash TS, which we've just installed. We need use router from next slash navigation. Whenever you're importing user router, don't accidentally import it from the router because that does not work in the app folder. So that only works in the pages folder. And now let's go ahead and import everything we need from React. So that's going to be the change event. We're also going to need to use effect and we're going to need to use state. And one more thing that I forgot to add is the actual input component. So npx chat CNUI at latest. Let me just expand this as much as I can. Add inputs like this. So we need an input component. Let's do npm run dev again. Let's refresh localhost here. And then what we're going to do is we're going to give this div a class name of pull width and a relative. And let's add a search icon here. And let's add an input here. And you can import input from components UI input like I did right here. Great. So now if I expand this, there we go, we can see the search icon and we can see an input. So now let's align them together so it looks better. So first things first, I want to style the input accordingly. So let's give this a class name of pull width. But let's limit how wide it can go by limiting into 516 pixels, which is just my random value that I found out looks the best. you can of course modify it and let's give it a padding the left of nine so it has a space for the icon to fit in so i'm going to give this search icon a class name of absolute top one and a half left three transform minus translate dash y one and a half text muted foreground height 4 and width 4 so just make sure that this is minus translate minus y right because this also works but you need to use minus so it does the opposite of this and then it's centered in the middle great and let's go ahead and give this input a placeholder of search boards placeholder, placeholder without a typo like that. And there we go. You can see how we now have our search here. I can even zoom in a little bit. And you can see how we have a nice little icon here. And you can see how we have enough space both to write and to actually render the icon perfect. And now let's modify it so that once we write something and after a debounce, we added that to the URL query just as we did with the favorites true like this. So we're going to add a little search here. So we can do that thanks to the change event, use effect, use state, use the balance query string and use router. So let's go ahead and let's get the router. Then let's go ahead and let's set the value and set value to the use state. And by default, let's make it an empty string and then let's debounce the value using use debounce and let's pass in the value and the amount of delay which for me is going to be half a second and then let's do const handle change to take in the event which is a type of change event and in pointy brackets it accepts the html input element and then in here we're very simply going to set the value to be event.target.value and then let's go ahead and write the use effect and let's add the dependency array to use the debounced value so not the value only the debounced value but also the router because we're going to need that to redirect the user. Now let's generate the new URL using query string dot stringify URL. The URL is going to be slash and the query is very simply going to be search and pass in the debounced value inside. And I also like to pass in this options as the third argument, sorry, the second argument, actually skip empty string true and skip now true. So we're never passing those in the URL. And then you can do router.push and paste that URL inside. And all you have to do is assign this handle change to the input here. So on change and passing the value. So it's a controlled input. So now if I try and type something, that's going to appear inside of my URL. So I'm going to write test here. And there we go. In my URL, I have a query search test. Perfect. Right now that isn't doing anything, but later we're going to use that to change what we are seeing right here on the boards. Perfect. So now what I want to do is I want to modify how this looks when I'm on mobile. So I want to add an organization switcher here, which will push this to this side right here. Let's go back inside of the navbar component right here. and right here after we do the search input i want to open a div with a class name block on small devices on large devices is going to be hidden and it's also going to take full space when visible so you can see how this already on mobile mode took up this space right here and pushed my user button to the end here so you can see how on desktop it's still everything is the same, but on mobile now my user button is pushed all the way to the side because of this div. And what we're going to render inside is actually the same thing that we are rendering inside of our organization sidebar. So go back inside of the organization sidebar and find this organization switcher, which we've modified. So it looks better. So go ahead and copy it, go back inside of the knob bar and paste it here. And you can import the organization switcher from clerk next to JS as well. There we go. And let's just confirm that everything can stay the same. So display flex makes sense. Justify content center makes sense. Align items center makes sense. The width 100% makes sense. But let's also add a max width the same way we did for our search input. So I'm going to give this a max width of 367 pixels, because I found that to be, you can see how it doesn't expand longer than this, right? So it kind of has the same behavior as our search where it stops expanding at some point because otherwise it just looks weird taking up the entire space. Great. And now what I want to do is I want to add a button here to invite members inside of any organization that we are in. And let's go ahead and do that. We're going to have to create a new component for that. So let's go ahead inside of components here and let's create a new file called invite-button.tsx. And let's go ahead and import plus from Lucid React. Let's import organization profile from clerknext.js. And let's go ahead and let's import add components UI dialogue and let's get the dialogue let's get the content and something to open the dialogue with so dialogue dialogue content and dialogue trigger and let's also import a button component from components UI button and then let's export const invite button and very simply whoops we're going to go ahead and render the dialogue dialogue trigger we're going to give it an as child prop and it's going to wrap a button from chat cnui which is going to render the plus icon and invite members text and let's give this invite all right and let's give this plus a class name of height for width for and margin right of two and let's give this button a variant of outline. And then very simply in the dialogue content, we're going to render the organization profile like that. So now if we save this file and go back inside of the navbar component, we can add this after our div wrapping the organization switcher. So I'm going to add invite button from dot slash invite button like this. So the same way we imported the search input and there we go. And we now have invite members, but you can see that the div, uh, doesn't really match the size of this component. So we can resolve that by going back inside of the invite button here and let's go ahead and give the dialogue content, uh, some props. So class name is going to be zero padding. BG is going to be transparent. Border is going to be none. And max width is going to be 880 pixels like that. So now if you go ahead and look at it, there we go. Perfect. So now you can always click on invite members here. And that's going to take you to the members page right here. And you can easily add new invitations. Great. But here's the thing. I only want to show this if the user has an active organization. So if they are not in any organization, for example, if I go here and if I remove this organization right now, right? If I do this, there we go. You can see how this will say no organization. So I think that yes you can see how this is now an empty thing because it doesn know to which organization are we supposed to add someone to So what we going to do is we only going to display that in the navbar if we have an active organization. And we can do that using the use organization hook from clerknext.js. So let's get the use organization hook from here. And let me just collapse this element. And in here, I'm very simply going to add the organization, use organization. and we are only going to render the invite button if we have an organization like that. So now if you take a look at it, we should not be having this. Let me just refresh so I can confirm or maybe this will pick a random organization here. Let's take a look. I think I need to destructure the organization. My apologies. Yes, I need to destructure the organization from the hook. There we go. So if we don't have an active organization, the invite button is not shown. But if we go ahead and select a random organization, there we go. We can click on invite members here. Perfect. So we have the working search. We have the working user button. We have the invite. We can switch to favorites. We have organization switchers. We even have a special view on mobile. Perfect. So now we are ready to go ahead and actually start creating some empty states for our favorites and our team boards. And then slowly, we're going to create a database schema and start creating our boards. Great, great job. So now that we've wrapped up our navbar, I want to go ahead and create this dashboard root page right here. So let's go ahead inside of the app folder, dashboard page.tsx. And let's go ahead and give this a class name of bg red 500. So we can see exactly how much space we are taking. So now I also want to give it a flex one option, and I want to give it a height and calculate 100% minus 80 pixels like this. And let's also give it a padding of six like that. There we go. So now you can see that our root page right here takes this entire space, but it doesn't take the height of the nav bar because we reduced that from the full height. Perfect. Now what I want to do is I want to add an example of an empty state. So I want to handle all empty states first. First one is going to be when the user doesn't have an organization. So for that, you can find any image you want, or you can go inside of my GitHub repository, go inside of public and find elements.svg like this. So this is a part of a 3D illustration pack called Sally. I'm going to leave the link in the description for the full pack as well. And it is allowed to use in both commercial and personal products. And now just drag and drop that inside of your public folder. And just make sure it's renamed to something like elements like this, which you can then type and add as an image. Great. let's head back inside of our app folder, dashboard page.tsx. Let's remove this BG Red 500. And let's go ahead and forcefully show the empty state for now. So I'm going to add empty org, like that. And now let's go inside of our underscore components here. And let's create a new file empty org.tsx. And I'm going to go ahead and export const that component so that we can import it in page and fix this error which we are having. So empty organization from dot slash components empty organization. And there we go. Now we have a text which says empty. And now I want to go ahead inside of here. And I want to give this div a class name of hful flex, flex call, items center, and justify center. Like that. And now inside of here, I want to render an image from next slash image with a source of slash elements.svg, an alt of empty, a height of 200, and a width of 200 like that. And there we go. you can see how now we have this kind of an empty element here. And now we're going to add some information about what's actually this supposed to represent. So let's add an h2 element, welcome to and then the name of your app. So I'm going to call this board. And let's give this h2 element a class name of text to Excel font semi bold, and the margin top of six like that. There we go. And now we're going to add some information text below that. So in here, let's add a paragraph, create an organization to get started. And let's give this a class name of text muted foreground. That's small and margin top off two, like that. And now I want to go ahead and add a button here, which will open this dialogue to create a new organization one more time. So let's go ahead and import everything we need. So we need create organization from clerk next JS. And we need the button from components UI button. And we also need a set of components from our components UI dialogue. So that opens up in a dialogue. So dialogue, dialogue content, dialogue trigger like that. Let's go ahead and below this paragraph here, add a div with a class name of margin top six. And let's add a dialogue. Let's add a dialogue trigger here, which is going to wrap our button. So let's give this an as child props so it doesn't mess up the styles or cause hydration errors. And we're just going to say create an organization. Actually, let's just write create organization like that. And I'm going to give it a size of large as well like that. And then below that, let's add a dialog content and let's render the create organization component. And let's give our dialog content a class name of padding zero, BG transparent, border none, and max width of 480 pixels in square brackets like that. So now this is how it's going to look like if this is the user's first time joining and they haven't created, been invited, or selected any organization. Sorry, organization. So then when they click here, there we go. They can create a new organization. Perfect. So we have that working as well. When I refresh, we're going to see that new organization right here. Great. And now I only want to show this if the user doesn't have any organization selected or if it doesn't have any organizations at all. So we can do that quite easily by going back inside of our dashboard page here. and we can turn this entire thing into use client. Since we're using convex, we don't need to do the whole React server components in this tutorial as much as we focus it, focus on that in other tutorials. So this one is gonna be a bit more familiar for the single page application folks. And in here, let's add use organization from clerk next JS like that. And let's go ahead and let's extract the organization itself. And then what I'm going to do is I'm going to dynamically render this. So if we don't have an organization, so exclamation point organization, then I'm going to go ahead and conditionally render empty organization component. Otherwise, I'm going to add a paragraph which will say board list like that. There we go. You can see how now it says board list because I do have an organization. But if it happens that I create a completely new account, or if I delete this organization, let me just copy the name and delete this organization. There we go. This is how it's going to look like. So when there is no organization selected, then it's going to prompt you to create an organization to get started. Perfect. So we've wrapped that up. Now what I want to do is I want to create the empty states for when we actually have an organization, like an empty board list. I also want to do a different empty state for when we search for something and a third empty state for when we click on favorites right here. So we need to create three more empty states. So let's go ahead and create this board list component. But before we do that, I want to show you that we can actually access the parameters like favorite and the search from our URL every time that we are working with a page convention component or a layout component. So what we can do here is we can create an interface dashboard page props. And in here, we can import search params. Sorry, we can destructure search params to have an optional search, which is a string and favorites, which is a string as well. And once you do this, just make sure and confirm that inside of your search input, you are using the search query. So that should match what you're trying to destructure here. And in your organization sidebar, when you click on favorites, you add favorites to the query. So just make sure there are no typos because this is what we are going to be extracting from the URL. And then you can assign that here. So dashboard page props, search params, and let's go ahead and let's JSON stringify search params like that. And there we go. You can see how it says favorites true because I'm on favorites. If I go back here, nothing is inside of my query. But if I search for something, there we go. My search is a search. Perfect. So just confirm you have those because we're going to use them to pass them to our board list component, which we're going to create now. So instead of this paragraph, we're gonna add a board list component. And we're gonna go ahead and pass in the organization ID as a prop to be the current organization dot ID. And the query is gonna be\nsearch params. So we know how to load the data. And then let's go ahead inside the underscore components and create a new file board dash list dot TSX. And inside of here, we're going to mark this as use client. And let's create an interface board list props to accept an organization ID, which is a required string, and a query, which is going to be required, but the props inside are going to be optional like search and favorites. There we go. And then let's export constant board list. Let's assign the props, board list props. And let's get the organization ID and the query itself. And then inside of here, we can just return a div and let's do JSON stringify query one more time to confirm that this is working. So I'm going to go back inside of my dashboard page dot TSX and this time import that the same way we did with an empty organization. And right now, there we go. I can see an empty object. If I click on favorites, the favorites are here. If I search in something, the search is here. Great. So let's head back inside of the board list. And this is what I'm going to do. I'm going to create a constant called data, which is going to be empty for now. And this is going to be to do change to API call. So for now, we're going to pretend that we always have empty data. And then what we can do is we can create a different empty states. So if we don't have data length, like that, but we have query dot search, that means that the fact that the data is empty is because users searched for something that doesn't exist. So we can change our return to say, try searching for something else, for example. And then let's go ahead below that, and let's add, if again, we don't have data length, and if we also have query.favorites, In that case, we can return a div saying no favorites like that. And we can also use the third one if there is no data length at all. We can just use return and let's add a div no boards at all like this. So the order of this actually matter, right? If you just copy and paste this at the top and put it above this, then this would always initially render, regardless if you have favorites on or search for anything. So the order of this matters. Or if for any reason you want to change the order, then you're going to have to be more specific with this query. So if you don't have data length, and if you don't have query.favorites, and if you don't have query.search. But it's just easier to make this an last if clause. So it is only going to show if none of these are true. Perfect. So let's try that out now. So as you can see, because I have something in my search, it says try searching for something else. But if I completely remove it, it says no boards at all. And if I click on my favorites, it says no favorites. So confirm that you have all three behaviors working. Perfect. And now what I want to do is I want to copy the assets, which we're going to need to create these three separate empty states for an empty search, for empty favorites, and for empty boards. So head inside of my GitHub repository right here in the public folder, and go ahead and download empty favorites, which I've prepared here. So just download this file. Then besides that, also download empty search and then also download note.svg. I forgot to rename this one, but I'm going to keep it as note.svg so that you can find it as well. Let's just wait for this to load and download for me. There we go. So now I'm going to drag and drop all of those inside of my public folder here. So let's go ahead and drag and drop note dot SVG, empty search and empty favorites. There we go. So make sure that you have all of those here. Let's just take a look. So we have empty favorites, empty search, and we have note dot SVG. Make sure all of them are named appropriately. Great. And now let's go ahead and let's create these different elements. So inside of our dashboard underscore components right here, I'm going to create a new file called empty search dot dsx like that. And inside of here, I'm going to import image from next image. and I'm going to export const empty search and very simply return a div which renders that image which is a self-closing tag and let's give it a source of slash empty dash search dot svg let's give it a height of 140 and a width of 140 as well and an alt of empty like that and then an h2 element no results found let's give this a class name of text to excel font semi bold and margin top of six and let's add a paragraph try searching for something else and let's give this a class name of text muted foreground and text small and margin top of two and let's give this wrapper div a class name of H full flex flex column items center and justify center like that. And then we can head back inside of our board list component. And in the first case here, where it says try searching for something else, instead, we're going to render empty search like this. There we go. So now when you search for something and if our API returns nothing, this is what will appear. But if you remove it, then it's going to be a different empty state. If you go to favorites, it is a different empty state again. Great. So now we have the boilerplate for our empty state. So let's go ahead and copy this empty search and paste it here. And let's rename this one to empty favorites like that. Let's go inside of empty favorites and rename it here. And in here, I'm going to use the source empty favorites like that. And we're going to change the text to say no favorite boards. And we're going to say try favoriting a board. Go back to the board list. And in this case, if there is a query of favorites, use the empty favorites component like this. So we have empty favorites and empty search. So if you try now and click on favorites, there we go. No favorite boards. But if you try searching for something, it's going to say no results found. So the last one we need is this one. And this one is also going to include a button to create the first board. So let's go ahead and copy the favorites or the search and call it empty. Let's call this empty boards like that. Let's rename this to empty boards. There we go. And then what we're going to do is we're going to use the note.svg like that. And let's give this a height of 110 and a width of 110 because it's going to be a bit smaller. And let's go ahead and give an H2 element of create your first board. And let's go ahead and give this a paragraph. Start by creating a board for your organization. And below that, we're going to add a div with a class name, margin top six. And inside, we're going to add a button component from components UI button. So just make sure you import that. And in here, I'm going to give it a create board text and a size of large. That's all it's going to do for now. And let me just go ahead inside of empty favorites here. and actually we don't have to do anything here. We can simply go inside of board list and then in this last if clause, simply return empty boards from dot slash empty boards. And there we go. We now have three empty states. The first one is if we don't have any board at all. So we're gonna use this button to create a board later on. We have empty favorites and we have an empty search. Beautiful. So we covered all of the cases and now we're finally ready to start creating our database schema and actually connect to the Convex API. You're going to see how easy it is to do that. It's very productive to work with Convex and we're going to finally start seeing some boards here. Great, great job. Let's go ahead and let's create the schema for our boards. So for that, I'm going to go ahead inside of my convex folder right here, and I'm going to create a new file called schema.ts. Inside of here, let's go ahead and let's import v from convex values. And let's go ahead and import define schema and define table from convex slash server like that. And then let's go ahead and export default define schema here. And let's add boards to be define table. And let's add a title to be a type of string. Let's add organization ID to be a type of string as well. Author ID to be the same thing. Author name as well. And we're also going to have an image URL. And now let's add an index for faster querying by organization. And let's define the field to be organization ID. And let's also add a search index for searching. So we're going to add search title like that. And let's define the search field to be title. And let also add filter fields to be organization ID because we only going to search inside of a single organization grid So we have our initial board schema as simple as that And just ensure inside of your terminal that you have npx convex dev running. And now once you do some changes here, you can see how that's going to tell you that convex functions are ready. If you did anything wrong, you're going to get an error here. And I believe that already if you go inside of your convex, you're going to see right here I have boards right here. I can click on show schema and there you go. You can see the exact code which we've just wrote inside of our code is now visible right here in this dashboard. Great. So now what I want to do is I want to go ahead and prepare all the possible images that we're going to use for our boards. So for this tutorial, I picked 10 random images from Undraw, which I will also leave a link in the description. So here is what I want you to do. I want you to go inside of my repository or simply go to Undraw. And in here, I want you to download this entire placeholders folder. So basically all of these files inside. So I'm going to pause the video and I'm going to add all of those inside of my new public folder. So I'm going to go inside of the public folder and I'm going to create a new folder called placeholders like that. And I'm going to download all of these files here. And there we go. So I just dragged and dropped all of these files here. So make sure that you have at least a couple of them. The more you have, the more random they're going to be every time you create a new board. And just ensure that they are inside of your public folder under a placeholder subfolder. So let me just close this and try and expand this. There we go. So public folder, placeholders, and inside I have 10 images all from Undraw. So I'm gonna leave the description for that as well, or you can simply download them from my GitHub repository. Great, so now let's go ahead and let's create an actual API route, which is gonna be used to create a board. So for that, we do something as simple as going inside of Convex and creating a new file called board.ts. And this is our new API endpoint. And then in here, we can do export const create and call a mutation. And we can import mutation from dot slash generated slash server like that. And let's also go ahead and let's import v from convex values like that. And now inside of here, let's go ahead and open up this mutation. And let's go ahead and pass in the arguments, which we expect every time we create a new board. So we expect the organization ID, which is going to be v.string. And we also accept the title, which is going to be v.string as well. And now let's go ahead and add a handler function, which is going to be an asynchronous function, which has access to the context and the arguments from above. And now with this, we can go ahead and get the user identity. So identity is equal to await context out get user identity. And then in here, we can check if there is no identity, throw new error unauthorized. There we go. So now we have full authentication inside of this convex backend, which you can see is not in route handlers, but in its own folder. So that's why we had to do the alph.config.js with the template and all of those other configuration elements. Perfect. So now I want to create a little array of images here. And what I'm going to write inside is basically a list of all the images which I have inside of my placeholders right here. So let me go ahead and show you how this is supposed to look like. So your images should look like this. Placeholders slash one slash two, three, four, five, six. And make sure that you add a root slash right here at the beginning. and then the name of your folder and then the name of the image you are using. So just make sure that that matches. So you don't have to write public, but you do have to write slash placeholders and then simply make sure that they match inside. Great. So now what we can do is we can pick a random image from this array of images right here. So let's go ahead and get a random image. And let's make that images. and then math.floor, math.random. And let's go ahead and simply multiply the random function by images.length. There we go. And now we have our random image and now we can write const board to be await context.database.insert and choose the schema, which is boards and very simply pass in the fields we need. The title is going to be arguments.title. organization ID is going to be args.organization ID. We're going to have an author ID, which is going to be identity.subject. And we're also going to have author name, which is going to be identity.name. And let's also pass in the image URL, which is going to be a random image. And since we know that our user will have a name, you can go ahead and put an exclamation point right here, like that. And in the end, just return a board. And there we go. That's how easy it is to create our API route with convex. Now what we have to do is we have to create a little button right here, which will fire, well, we have to create a functionality that when we click on this button, it actually calls this API mutation. And then we're going to go ahead inside of right here in the database and see if that has created it or not. So let's go inside of our empty board component. So inside of app folder dashboard components, we have an empty boards component right here. And let's go ahead and mark this as use client. And let's go ahead and let's import use mutation from convex react. and let's also import API from convex generated API. And then what we can very simply do here is const create to be use mutation API.board.create. So you can see how we have exact typings here. And then what we can do is add const on click, create, and let's pass in the title to very simply be untitled like that. and let's pass in the organization ID. That's gonna be, we can get that from organization right here using use organization from clerk next JS like that. So inside of here, let's break the function if there is no organization like that. And then we can safely use this organization, whoops, dot ID here. And there we go. We no longer have any errors. And now let's add this on click right here. On click. There we go. So let's check it out now. If I go ahead and click create right here, and if I check it, there we go. You can see that it is immediately added inside of our database. We, of course, cannot see it here because we are not rendering any of them. If you are not seeing this, make sure that you are importing useMutation from Convex React and make sure that your typings are correct. api.word.create, you can see how I can click here and it will lead me to that mutation right here. And also, you can always check your terminal just to confirm that there are no errors in your Convex functions right here. Great. So we have that now. and you can see that immediately once I click here, it adds a new one. So here's what I want to show you next is that what I like to do is I like to add a little loading indicator while this is happening, even though it is extremely fast. But I kind of don't want to do it every single time. So I'm going to show you how we can create a reusable mutation hook so that we always have our loading exported from here. because right now we only have the action itself. So let me show you a way we can do this. So let's go inside and create just a new folder in the root of our application called hooks. And inside, I'm going to create useAPIMutation.ts. Let's go ahead and let's import useState from React. Let's import useMutation from convexReact. And let's export const use API mutation. We're going to pass in the mutation function to be a type of any. And let's define the pending and set pending here to come from use state, which is false by default. And let's define the API mutation to be use mutation mutation function. And then let's add const mutate to accept a payload of type of any. Let's do set pending to be true. And then let's return API mutation. Let's pass in the payload. Let's add dot finally to simply set pending back to false. and dot then will get the result and very simply return the result. And if we get an error, let's go ahead and throw that error back. There we go. So what did we achieve with this? Well, you're going to see in a second, what we have to do from this hook is return mutate and pending like this. And now what we can do inside of our empty boards, instead of having to call this use mutation, and then in every single on click, we would have to manually add pending here. And then we'd have to do that finally here and change the pending back to false. And we would have to do that for every mutation that we do What we can do instead is now use the following use API mutation from our or hooks API mutation and then in here we now have execute sorry mutate and pending So we can very simply just call mutate here which will work exactly the same And then on this pending right here, we can add disabled pending like that. So now if you try this, you can see how for a second my button is disabled. And let's just confirm that this is still working. Now you can choose whether you want to use the first method or the second method. Both will work for this tutorial. They're not that terribly important, but I kind of like my method. The only thing that I really don't like is that I couldn't get the types to work. So we lose on the types, which is pretty important. But I just think it's fun for a tutorial for us to explore how to create this custom abstractions around hooks so you learn something new. This kind of reminds me of using a tan stack query mutation hook where we have, you know, mutate and the pending state here. If you don't like it, if you think this is a too big of a trade-off by using any, feel free to use the initial version in where we use the use mutation here directly. And then you simply have, you know, mutate or whatever you want to call it here like that. And then you simply won't have access to this disabled is pending. So it's your choice. I'm going to be using use API mutation throughout the tutorial. So you can either write it from here or copy it from my GitHub. Great. So now that we know how we can create our boards, let's go ahead and add some toast notifications. So it indicates to us whether this was successful or not. We can do that by going inside of our terminal here. Let me just open a new one. and do npx chat cnuif latest add sonar like that so that's going to add the sonar toast package inside of our project so we now have one file to add which is called toaster so go inside of the app folder layout.psx and let's import toaster from components ui sonar and then we're simply going to go ahead and render that. Well, you can just render it inside of the body or inside of Convex Client Provider like this. And then let's go inside of our empty board where we added this on click right here. And very simply what we can do now is we can attach a dot then from where we're going to get the ID of a newly created board. And let's do toast, which you can import directly from Sonner, like that. We're going to do toast.success, board created, like that. And I'm going to add a to-do, redirect to board slash ID. And then let's add .catch. And this one is already simply going to call toast.error, something, actually, let's be more specific, failed to create board. Like that. There we go. So now when we go ahead and create a new board, there we go. We have a message board created and that is true right here. Perfect. And you can see how we have the matching organization ID. The title is untitled by default. And I believe that most of this will have a different image placeholder. I don't know if I can expand this so we can actually see that. But nevertheless, we're going to be able to see it in a second where we actually start rendering these things. And here's the cool thing about Convex. So at any point, you can click on this functions right here. And there we go. You can see that the functions which we wrote inside of our VS code are actually visible here. Even the images array is visible inside of Convex. So that's what I think is really amazing about Convex is that it manages to replicate your local code inside of its cloud right here. And in here, you can see the exact operation which we wrote. So it's very, very easy to debug things in production. It will be amazing if we had this for every database that we are using. You can also manage the invocation, the errors, the execution time. And let's go ahead, for example, and add a little console log to that mutation of ours. So we're going to go inside of convex board.ts. For example, let's add console log random image. And let's do test here. and I believe that we're already going to be able to see those logs so if I create something first let's see if this has updated there we go you can see how it has immediately updated the code right here so if yours hasn't make sure that in your terminal you are running npx convex dev or if you're having any errors fix the errors or simply restart the terminal and let's go ahead and click on logs right here. And I think we should be having some logs. I'm just not sure how I can access them or where, but I did manage to do this initially, I think. Nevertheless, you can explore this yourself. Not only do you have that, you also have like scheduled cron jobs and a bunch of different things which can run in the background. It also has its own file upload. So it's a very powerful database and all of it is completely real time. So you're going to notice later when we add a query here to load our boards, we're never going to have to refresh that query. It's just automatically going to pull the newest from the database. So the whole application has a very optimistic feel, which will fit perfectly into what we're trying to create here as a Miro clone. Perfect. So what we're going to do next is we're going to go ahead and actually display our boards right here. Great, great job. Now let's go ahead and let's find a way to actually display this boards, which we know that we have inside of our database. So for that, I want to go ahead and go inside of the convex folder and create a new API endpoint, which is going to be called boards. So multiple inside of the board individually, we're going to use that to create it, to update it, and to delete it. But for the boards, we're going to use that for all the querying of multiple boards. So I want to separate those two. All right. And let's go ahead and let's import v from convex values. And let's go ahead and let's import query from dot slash generated server. And let's export const get to be a query, which will accept the arguments of for now, let's just do an organization ID, which is going to be a required argument. And let's go ahead and give it a handler, which is an asynchronous function, which has the context and the arguments like that. Now inside of here, let's get the identity. And let's do await context.out.get user identity. If we don't have the identity, we're going to throw new error, unauthorized, like this. And now what we can do is we can fetch all of our boards. So we can do that by using const boards to be await context.database.query boards. And then we can add with index. And you can see in here that we already have one index which comes automatically and we have another one which we've created so if you remember inside of our schema we've created an index by org so we can use this index and then we can use this argument for faster querying we could have of course used the manual equivalent checker but remember that is not an index that will use file sort whereas this will use an index which is much faster for a lot of queries. And now let's go ahead and use this. Now we have access to the query. Let's do query equals organization ID arguments dot organization ID. And let's go ahead and let's order all of this by descending and let's collect everything. There we go. And all we have to do is return the boards like this. That is it. That is our API route to get all the boards. Beautiful. Now let's go ahead inside of the app folder, dashboard components, board list right here. And inside of here, now let's go ahead and let me just expand all of this here. Now we can import use query from convex react. And let's also import API from convex generated API. And now in here, what we can do, just make sure this is marked as use client, is we can replace this data from being an empty array to instead be use query and pass in API.boards.get. And we can pass in organization ID as the argument which we have from the props like that. And in here, I can write if data is undefined, that will represent the loading state. So I'm going to open a parenthesis, write a B, loading. So data can never be undefined, regardless if there is an error or if it is empty. If it truly doesn't exist, convex is going to return null for data. But if it's undefined, that means that it's in the loading phase. So you can safely use undefined check to determine whether data is loading or not. Beautiful. And now let's go ahead and let's actually stringify the data itself. And there we go. You can see how now we have an array and the objects inside of all of our boards which have, you can see how everyone has a different placeholder. Nice. So let's go ahead now and actually try and render them. So for that, we're going to be using a new component called board card. but before we do that let's just go ahead and add an h2 element here and let's write team boards and let's write let's give this a class name of text 3 excel like that and let's make it dynamic so if query has favorites in that case we're gonna write favorite boards so let me just copy this here So if it has favorite, it's going to be favorite boards like that. So now if you switch to favorites, it says favorite boards like that. Great Now inside of here let open up a div and let create a grid here So grid grid calls one on small devices grid calls two on medium grid calls four on large grid calls four again on extra large grid calls five on to excel grid calls six and let's also add gap five margin top of eight and padding bottom of 10 and inside of here we're going to go ahead and iterate over data dot map we're going to get the individual board and we're going to go ahead and render a board card element which currently does not exist but we're going to create it in a second Let's pass in the key to be board underscore ID. Let's pass in the ID to be board underscore ID as well. We're going to do the same thing with the title. So board dot title. Let's go ahead and pass in the image URL, which is going to be board dot image URL. Let's pass in the author ID, which is going to be board author ID. And let's do author name to be board author name. and besides that we're also going to pass in the created at prop which is going to be board dot underscore creation time and let's also pass in the current organization id for that for that board of course so not the current organization id but for the board created and lastly we're going to have is favorite which for now we're going to manually write to be false Great. So now that we have that, let's go ahead and let's actually create our board card element here. So I'm going to create a folder for that because it's going to have some other elements like our sidebar. So I'm going to create a folder board card and inside I'm going to create an index.tsx like that. Let's go ahead and mark this as use client and let's export const board card and let's return a div board card. and now go back to the board list, and you can import board card very simply from dot slash board card. So all of this are on the same level. As you can see, board card, board list, all of this are inside of our app folder, dashboard underscore components, and there we go. You can see that we now have board cards in a grid like this. Great. So now let's go ahead and let's fix this TypeScript errors, which we have right here. So we have to allow all of these types inside of our board card element. So let's go ahead and let's create an interface. Board card props to have an ID, which is a string, a title, which is a string, author name, which is a string, author ID, created at. Created at is going to be a number, image URL, which is a string, organization ID, which is a string, and is favorite, which is going to be a Boolean. And now we can go ahead and assign all of those here. So board card props, and we can safely extract all of those. Author ID, author name, created at, image URL, organization ID and is favorite. Great. And now in your board list, you should no longer be having any problems with this. Great. Now let's go ahead and let's import our link component here from next link, which is going to be wrapping the entire board card. So it's going to be a link and the href is going to go to slash board slash the individual ID of the board. then inside of here let's create a div with a class name of group let's create an aspect with a custom value of 100 slash 127 border rounded large blacks blacks call column so all the items inside are one beneath another justified between and overflow hidden and now let's go ahead and add a div here with a class name of relative flex1 and vg amber 50 that's going to be the background color for all of our cards as you can see right here all of our cards have a slight amber color now and now we're going to go ahead and render the individual placeholders inside using the image component from next slash image so just ensure you've added that. And let's go ahead and give them a source of image URL. Let's give it an alt of a doodle. And let's go ahead and give it fill property and class name of object bit. There we go. And now you can see all of our beautiful random images, which were created every time we clicked on create a new board. So if you're seeing some empty images, go inside of your database here and just confirm exactly what you're seeing inside of image URL. So you can expand it like this and confirm that this placeholder, slash one SVG, eight, two, seven, anything that you have, you actually have inside of your public folder as we had to do when we added those So inside of your public placeholders, all of those should exist inside of here. If you don't have them, you're going to get a broken image picture right here. Great. So we've added that now. And now instead of having alt of doodle, I think it's better that we actually give the title off the board. I think that makes more sense. Great. And now what I want to do is I want to create an overlay component. So let's go ahead inside of our board card folder and create a new file overlay.tsx, which is going to be a very simple export const overlay. And all it's going to do is return a div, which can even be a self-closing tag. and let's go ahead and pass in the class name opacity is zero group hover opacity 50 transition opacity h full with full and bg black like that and now you can go inside of the index right here for the board card and very simply just below the image add the overlay component from dot slash overlay like that. So just make sure you import this, which we've just created. And now I believe that when we hover, it gets a darkened background. So the reason this works is because we have, when we hover on a group, we change the opacity to 50. And this has a BG black color, right? And how do we how does the group get hovered? Well, it's because the parent element has the group class name. So everything which is inside of this div, if it has a class name which reacts to the group, it's going to work like this one. Great. So why do we even do that? Well, we do that because once we hover, we're going to have a little options bar here so that we can rename our board, delete our board, or copy the URL for our board. So it's going to be more visible once we hover on it. So it indicates which one is it that we are hovering on and the button itself is going to be more visible. But we're going to do those actions later when we actually create the API routes for them. What I want to create now is the footer. And before we can build the footer, I want to prepare some labels. And that label is going to be the author label. So to show you who created this board and the created at label, which is going to format the distance from when it was created. So let's go ahead and install date FNS for that. So NPM install date FNS. So we can then import the package that we need. I'm going to go ahead and import format distance to now from date fns like that and in here let's go ahead and let's define const outdoor label is very simply going to check if the user id which we can very easily get from use out and you can either import it from clerk next js or you can import it from convex react but i'm going to be using clerk next JS. So you can try out both of those. So use out clerk next JS, that's going to give us the user ID. So we're going to check if the current logged in user ID matches with the author ID of this board. In that case, we're going to write you. Otherwise, we're going to just say author name like that. And now let's go ahead and let's write created at label. So that's going to use the format distance to now, and it's going to pass in the created at number, and we're going to add suffix to be true. All right, so now that we have those two, we can go ahead and we can create our footer component. So the footer component is going to go outside of this div, which is encapsulating our overlay and our image, but still inside of this last div. So this is what it's going to look like. We're going to pass in his favorite to be his favorite, which for now we are manually controlling with a boolean. The title is going to be the title. Author label is going to be author label, which we've just created in a constant. Created at label is going to be a matching created at label. And we're also going to have on click for now to be an empty arrow function and disabled for now is going to be false like this. Let's go ahead inside of the board card and create the footer.tsx element like that. And let's go ahead and let's import everything we need, which is the star icon from Lucid React. And we're going to need the CN library from Lib Utils. let's create an interface footer props to accept all of those things so it needs a required title a required author label a created at label is favorite which is going to be a boolean on click which is going to be\nan arrow function, and a disabled prop, which is going to be a Boolean as well. And now let's export const footer. And let's very simply assign those props. And we can extract the title, author label, created at label, is favorite, on click, and disabled like that. Let's go ahead and let's return a div inside, which is for now just going to print out a footer. Now we can go back inside of the board card and we can import the footer from dot slash footer the same way we did with the overlay. And there we go. You should just be seeing the text footer at the bottom of each of our cards. And now let's go ahead and actually use that author label and the created that label so it shows us some more information about the board. Let's give this main div a class name of relative, background white, and padding of three. Inside of it, let's open up a paragraph, which is going to render the title of each of our boards. And let's give them a class name of text. And let's specifically choose 13 pixels. Let's give it a truncate in case the title is too long. And let's give it a maximum width. and let's manually calculate 100% minus 20 pixels. The reason we are calculating 20 pixels outside of the 100% is because this is going to be the space we need to render our favorite button. So we're going to make sure that it never overlays, that the title never overlays with that button. And then below that, let's add another paragraph, which will use the outer label, comma, created at label. So it says you about an hour ago like that or whenever you've created yours. Now let's go ahead and style this. So the class name for this paragraph is going to be opacity zero group hover opacity 100 transition opacity. Text is going to be even smaller 11 pixels. Text is going to be muted foreground and truncate for that text as well in case it's too long. So as you can see, we won't be seeing the information unless we hover on it. And then we're gonna see some more information about our boards. Beautiful. And all of your boards should have the untitled title because that's the way we wrote our code for now. All right. And now let's go ahead and let's actually create this little button here. So it's gonna render the star icon, which we've imported from Lucid React. and let's go ahead and give it a disabled or disabled. Let's give it an on click of on click for now. And my apologies, we are not doing this to the star. We should be doing this for the button like that. And let's go ahead and give this a class name to be dynamic using CN. First, let's write some default ones. So opacity is gonna be zero, but when we hover on the main group, the opacity is going to go to 100 let's add transition let's add an absolute property let's position it in the uh three points from top let's also do uh three points from the right side text muted foreground of course i'm saying points but these are actual metrics so 12 pixels right i just wasn't sure which one it was. And on hover, we're going to do text blue 600 like that. And then we're going to add a dynamic class name. If we are disabled, very simply, we're going to do cursor not allowed. And we're also going to do opacity 75. Now inside of the star component itself, all we're going to do is add one more dynamic class name here, which is first going to define the width and the height of the icon and then if we are already favorited this we're gonna add fill glue 600 and text glue 600 as well like that there we go so let's try it out now now when i hover you can see how my little icon becomes blue when i hover on it and now let's go ahead and quickly change inside of our index. Is it board card? Let's go inside of board list right here and change is favorite to true. And now if we coded this correctly, this should change to a filled icon like this. Great. So let's bring it back to false. Now, of course, later we're going to make this actually dynamic. And now what I want to do is just give you an ability to add a new card a new board from this screen because you can see that it is missing right here. So let's go ahead and do that. Let's head back inside of our board list right here. And just before we iterate over our data and render the board cards, we're going to go ahead and render a new board button like that. So it's going to be inside of the grid. and let's go ahead and pass in the current organization id like this now let's go ahead and let's actually create this one inside of the underscore components here so new board button dot psx let's mark this as use client and let's export const new board button and return a div new board button. Go back inside of the board list and you can then import the new board button from right here. And now you should just see a plain text taking up this space and we're going to turn that into a nice blue button. So first things first, let's create an interface new board button props to accept an organization ID, which is a required string, but also an optional disabled Boolean. Let's go ahead and assign those new board button props, organization ID and disabled. I like that. And then inside of here, this is not going to be a div. Instead, it's going to be a button component. So let's give it a disabled of disabled. Let's go ahead and pass in onclick for now to just be an empty arrow function. And let's go ahead and give it a class name, which is going to be dynamic. So prepare the cnutil from libutils. So if it is not disabled, it's going to have your usual call span one property. Aspect is going to be the same one as from our board card, our board item. So it matches. And BG is gonna be blue 600, rounded LG. And let's also add while we are here on hover, but specifically on this board hover, it's gonna be BG blue 800. So it darkens. We already added rounded LG, that's good. So besides that, we're gonna need flex. flex call items center, justify center, and padding on both sides of six, like this, on up and down of six. Great. And now let's go ahead and let's modify this by adding an empty div here. So just so it takes up some space. That's the only reason. I want this to take up some space and then add a plus icon from lucid react so input plus from lucid react like that and let's give this a class name of h12 width of 12 text white and let's make it just a bit tinier by giving it a stroke of one like that and now let's add a paragraph new board and inside of here let's add a class name text small text white and font light there we go so now we have this setup right here and I forgot to add the actual dynamic class here so if we are disabled in that case opacity is going to be 75 percent like that. So let's try it out. New board like this. There we go. I think this looks fine. Let's just see. The text looks a bit big, in my opinion. Text small, so this should be enough. I don't know. You choose if you want extra small or text small, right? All right. And now what I want to do is I want to actually create the function so it creates the new board when we click on that and for that there are two ways we can do that if you remember so you can either use create using use mutation directly from convex react like that and then pass in the API from convex generated API so api.board.create and then you can create on click here and very simply in create you can just pass in the organization id and the title untitled like that and then you can pass the on click right here so if you try that out it should already be working so if i click new board there we go new one is created but i want some loading states and i want to use my use API mutation. So I'm going to change this to use use API mutation from my hooks use API mutation, and I no longer need this one like that. And let's go ahead and pass him API board create. And let's execute mutate and pending. So this is going to be mutate like that. I'm going to change this to use pending or disabled and this thing as well. Pending or disabled and let's see if there are any more places. I don't think there are any more places where this is needed. Great and now let go ahead and try it out So if I go ahead and click again there we go you can see how it disabled for a quick second And now let just add the toast notifications So toast from Sonor So after we successfully mutate, we're going to go ahead and get the ID of the new board. And then we're going to add toast.success board created. And I'm going to add a to-do redirect to slash board. id and let's add dot catch which is very simply going to go to the error and say failed to create board and we can already try that out so when i click here there we go board created this is loading nicely very very nice so now let's go ahead and let's actually create a proper loading element for our items right here. So for that, we have to quickly go back inside of our board card component inside of index right here. And we also have to add the skeleton component. So let's go inside of terminal here and let's run npx chat CNUI. At latest, add skeleton. so skeleton like this and then let's go ahead and let's actually import the skeleton so import skeleton from components ui skeleton and in here we're going to write board card skeleton is going to be a function board card skeleton and let's go ahead and let's return a div which is going to be exactly the same as this one so you can actually copy this div because we are trying to replicate what's actually going on but we actually don't need a border itself we do need rounded lg we don't need flex or flex call we do need overflow hidden and we also don't need group and then we can just render the skeleton inside and make sure it fills 100% of this space. There we go. And now we can go back inside of our board list and then instead of just rendering this loading right here, let's go ahead and make this a bit easier for us to see. So I'm gonna change this manually to always be true so that we can always see the loading text in here. So now we're gonna change that to actually display our elements. So for that, I want to actually copy this first two elements. First three, actually. So I copied the div, I copied the h2, and I copied our grid. So let's just make sure we have all of those like this. So you should have the div, you should have the h2, which can just say, well, it can work like this, query.favorites, favorite boards, or team boards. and then this div is just a grid and inside we are going to render the new board button but we're gonna make it disabled so pass in the organization id and also pass in disable and then in here we're gonna pass the board card the skeleton like that and copy it four times or however many you see fit and there we go this is going to be our loading screen you can see how it kind of shimmer. So you can see how this button is disabled. Oh yeah. And also when a button is disabled, perhaps we should not change its color. So let's go inside of a new board button. And we're also going to add on here, hover, VG, blue, the default one like that. And let's add cursor not allowed also like that great so let's try it out now there we go so now it doesn't change color so this looks like a very nice loading skeleton right now great and i think the same will be true in our favorite boards great so let's go back inside of board list now and let's change this to be only if data is undefined so only while it's loading is that screen going to appear so when you refresh if you have a bit of a slower connection you can see how it actually displays that skeleton for a second perfect so we just created a nice way to render all of our boards what we're going to do next is we're going to add the functionality to load well first we're going to actually do all the actions right to rename them to delete them and to add them to our favorites and only when we add them to our favorites we're going to go ahead and figure out the favorite boards tab and how we're going to render only those. And then we're going to wrap it up with the search, which is going to be very, very simple. We can do it already, but I just want to go ahead and finish the actions for our cards. Great, great job. So let's go ahead and let's create the actions for each board so that we can copy the board ID, delete the board, and also rename the board. In order to do that, I want to add an element from ShadCNUI called drop down menu. So let's go ahead and run mpx chat cn dash ui at latest, add drop down dash menu. This is how it looks like in one line. So go ahead and add this. And while we are here, we can also add an alert dialog component. And we can also import the package we're going to need. So let's do that as well. So let's also add alert dialog, which we are going to need. And then we're going to install to stand. So NPM install to stand or to stand. I'm not sure how to pronounce that. So make sure you have drop down menu, alert dialog and to stand. And then let's do NPM. Actually, I think I already have it running. Let's see. NPX convex dev and NPM run dev. Great. I have both of those running. So I can just refresh this to confirm that. There we go. So now let's go ahead inside of our app folder, inside of the dashboard, inside of our components. I want to go ahead and actually not here because these actions are going to be reused in the board screen as well. So let's create them in our global components folder right here. So inside of here, go ahead and create actions.tsx. Let's mark this as use client. And let's create an interface action props. Actions props like this. And let's go ahead and define the children to be react react node. Let's add an optional side, which is going to be drop down menu content props, which you can import from Radix UI. And let's go ahead and let's get the side prop. And let's go ahead and do the same thing for the side offset and just pick the side offset prop. So you can import this from Radix because we added that using ShadCN UI. So we are only importing the props from Radix. We will not be using the actual component from Radix. For that, we're going to use UI components drop down menu. And now let's go ahead and give it an ID of string and let's give it a title of string. Great. And let's go ahead and create export const actions. Let's destructure these props. Let's extract all of them. So children, side, side offset, ID and title. And let's very simply just return a div saying actions. and now let's go ahead inside of our app folder dashboard components board card index.tsx right here and we're going to go ahead and put this just below the overlay we're going to add our actions component from components actions so this is how I imported that you can see that I put it in our global reusable actions folder because we're going to use this twice in the project and let's go ahead and pass in the id id for this board the title is the title and let's also add the side to be opened on the right and we are missing the children and for that let's go ahead and do it like this we're gonna pass in a button component and then inside i'm just gonna write I am a button for now. And now I believe this won't even be rendered anywhere. I don't think this is even visible. Let's just go ahead. So we have this div which says actions, but I'm not sure if we should even be seeing this. Let's go ahead and let's give this a class name. Absolute Z50 top one right one there we go okay so we can see it that's what I just wanted I just wanted to make sure that we can see this all right so what we're gonna do now is we're gonna go ahead and actually render this children right here so this button which we are supposed to see so right now we're not seeing that we are seeing this text actions so we're gonna go ahead and import everything we need from the actual components UI drop down menu, but not from Radix, from our components. So we're going to need the drop down menu, the drop down menu trigger, the drop down menu content, and drop down menu item, and also drop down menu separator. So let's wrap this entire thing inside of a dropdown menu. And then let's add a dropdown trigger inside where we're gonna just render the children and let's give it an as child props so it actually shows that button over there. And we can leave it like this for now. And now what we have to do is actually style the children which we are passing inside of the actions because this is where that is gonna be rendered. And as you can see, we cannot see it anywhere now, but I want it to appear right here. So let's go ahead and give this button some class names. And before we do that, let's simply add a little icon from Lucid React. So we're going to use an icon more horizontal from Lucid React. So import this icon and render it inside of this button right here Let give this one a class name of text white opacity 75 on hover opacity 100, and transition opacity. And I believe this still won't be visible because we have to style our top button here. So let's give this button a class name of absolute, top 1, right one, opacity zero, and group hover opacity 100, transition opacity, px3, py2, and outline none. And now let's check that out. And there we go. You can see how we now have an appearing more horizontal icon button right here. Of course, when I click on this, I'm redirected to a 404. We're going to use prevent default and stop propagation to fix that in a second. But once you hover on individual card, you should be seeing these three dots. And when you hover over them directly, they should be even more visible because we change their opacity. Great. So we are done with this part and now we can focus exclusively in the actions here. So let's go ahead and let's create the drop down menu content, which will open once we click on this. So drop down menu content like that. And let's go ahead and give it a side of side. Let's give it a side offset of side offset. Let's give it a class name of width 60 like that. And I want to add on click here and get the event. and anywhere where I click inside, I'm going to stop propagation so that I am not redirected once I click on this part of the component. And now inside, let's add a drop down menu item here. And in here, I want to render a link to icon from Lucid React. So just make sure you add this import. I'm going to add it here. Let's give this link to icon a class name of height for width for and margin right of two. and a text is going to be copy board link. And let's give the drop-down menu item itself a class name of padding3 and cursor pointer. So let's see if this improved anything. So now when I click here, there we go. You can see I have an option to copy the board link and I can click here and I can click here and I'm not redirected at any point unless I actually click on the card, then I'm redirected to a 404 page. But if I just go ahead and click on this little toolbar, I'm not redirected. And we achieved that by using onClickEventStopPropagation on the dropdown menu content. This is, of course, my solution for this. If you know any better solution to stop propagation from the link, feel free to use it. Feel free to leave a comment if you think there is a better way to do it. All right. And now let's actually implement this functionality so that we can copy a link. Since that is going to be very simple, we don't need any mutations for that. So const on copy link is very simply going to call the navigator.clipboard.writeText. And very simply, we're going to go ahead and do the following. So we're going to go ahead and call window.location.origin. slash board and then the individual ID. and then we can add dot then here because this is actually a promise so in here we can call our toast from sonar and let's write a success link copied and let's also do a catch even though this can very rarely fail i believe failed to copy link so i imported toast from the sonar package So just don't forget to do that. And let's now add the on copy link here to this drop down menu item. There we go. So now I believe this should already be functional. So if I copy this board link and let me paste here, there we go. I have a working ID and I have a toast. That link has been copied. If I try this one, you can see that it's a different ID. Perfect. So this is working. What I want to do now is I want to create the delete functionality. So the first thing I want to do for that is go inside of the convex folder and go in here in the individual board function. So let's go ahead and do the following. Let's go above the create here. Actually, let's go to the bottom here. And let's do export const remove, which is going to be a mutation. did we import a mutation we have a mutation all right so that's going to be a mutation which accepts the arguments which are now just going to be id which is a type of id and boards then we're also going to get the handler itself which is going to be an asynchronous function which has context and arguments and then in here we're going to get the identity and we're going to do await context out get user identity. And let's check if there is no identity. In that case, throw a new error unauthorized like that. And then what we're going to do is await context database delete. And very simply, we're going to pass in arguments dot ID. So we don't need to specifically say to delete from the boards because the ID is type of ID boards. So Convex knows that we are deleting the boards schema. So for now, we're just going to do it like this, but I'm going to add a little to do here. Later, check to delete favorite relation as well. So when we add favorites later, we're going to have to remove that user relation as well. Otherwise, we're going to have some bugs. but this should work just fine for now. So we can go back inside of our actions. So it's located in our global components folder right here, actions. And let's go ahead and let's add another dropdown menu item. So I'm gonna copy and paste this one and I'm gonna go ahead and use the icon trash to here from Lucid React and the text is gonna be delete. like that. And let's go ahead and let's import. I'm going to use my use API mutation hook from hooks use API mutation here. So I'm going to go ahead and extract that. So const right here, mutate and pending. It's going to be use API mutation, API from convex generated API.board.remove. So the reason I'm calling it remove is because I don't want to take the reserved delete keyword in JavaScript. So make sure you import the API from at slash convex underscore generated API. You can use my use API mutation or I already showed you you can use use mutation from convex slash react. And then you have to write your own pending if you want to do that. Both will work. And now we can add a function const on delete, which is very simply going to call mutate, passing the ID, which we have, all dot then, post success, board deleted, and dot catch, post error, failed to delete board. Like that. So let's go ahead and copy this, and let's add it instead of this one, since we copied that from the one above. So let's change it to a proper key. So now it should immediately be deleted. Once I click here, there we go. Board has been immediately deleted. You can see how it is working. What I wanna do now is I wanna add a little confirmation model before this happens because you can see that it's very easy to make a mistake and it happens really fast and there is no undo. So I'm gonna go ahead and create a reusable component called confirm model, which we can build because we have the alert dialogue. So let's go ahead inside of the components folder and let's create a new file, confirm model.tsx. Let's import use client. And let's go ahead and import everything we need from components UI alert dialog. And that's going to be alert dialog itself, alert dialog action, alert dialog cancel, alert dialog content, description. We're also going to need the footer, the header, the title. Whoops. So alert dialog title and alert dialog trigger. So all of these components here are needed to build this. Let's create an interface confirm model props to have children, which in our case is going to be our delete drop down menu item. So that's going to be react, react node. Then we're going to go ahead and have an on confirm function, which is going to be an empty void. Disable prop in case we want to disable that from the outside. A header to display a different message if we want to. And description, which is going to be optional, to display a more specific message if we want to use that for something else. So we don't need to use this for deletion. We can use this for whatever we want to confirm. And then let's export const confirm a model here. let's go ahead and extract this props. So confirm model props, children on confirm, disabled, header and description. And then simply inside, we're going to go ahead and return the alert dialog, then the alert dialog trigger, which is simply going to render the children and have the as child property. And then we can use the alert dialog content. which needs to be closed here. And inside, let's add the alert dialog header, alert dialog title. And in here, we're gonna render our header prop like that. And then just outside of the title, let's add alert dialog description in where we're gonna load the description prop like that. And then outside of the header let add a alert dialog footer component And in here very simply we going to have the alert dialog cancel which is just going to say cancel And below that, we're going to have the alert dialog action, which is going to say continue or confirm, whatever makes more sense to you. Perhaps confirm would be better. and now let's go ahead and let's give this a disabled prop of disabled and let's go ahead and give it an on click on handle confirm so we're going to build handle confirm now which is going to be very simple so const handle confirm is very simply going to call the on confirm option like that there we go that's it that is our reusable confirm model component so what we can do now is we can go back inside of our actions component. We can go ahead and import this. So I'm going to import confirm model from dot slash confirm model or slash components confirm model, however you prefer it. And then you can very simply just wrap the entire dropdown menu item in confirm model like that. I believe that we can do it like this. And what we have to do now is we have to pass in the title, sorry, the header, which is going to be delete board, question mark. We need the description, which is going to be, this will delete the board and all of its contents. And let's also give it a disabled prop of pending. and let's give it on confirm to be our on delete function, which we're using just below like that. And here's what I want to do. So I don't want to use the dropdown menu item because I believe this will now not exactly work. So let me just comment out this dropdown menu item on click. And now if I click on delete, you can see how the model opens and then closes very fast. So we cannot use the drop-down menu item as a child of the alert dialogs. Instead, what we're gonna do is we're gonna use the normal button here. So you can just import button from .slash UI button or components UI button. Let's go down here and simply use that, as simple as that. But I believe we have to style this button a bit. So let's give it a variant of ghost. And let's go ahead and give this a text small, a full width, justify start, and font normal. So it doesn't differ from our dropdown menu item component. And I think that now we're gonna have a nice confirmation flow. So there we go. The button doesn't really differ from the one above, but when I click here, you can see how I have a confirmation. If I cancel, nothing happens. But if I click confirm, then the board is deleted. Perfect. So we just wrapped up the confirmation model. Now we have to do a similar thing, but for the last option inside of these actions, which is going to be the option to rename the board to something else. So we can do that by first going back inside of convex board right here. And we're going to go ahead and create a new method called update. So let's write export const update to be a mutation. Let's get the arguments to take the ID, which is going to be v.id boards. And we are also going to take in a new title, which is going to be v.string. And then let's go ahead and write a handler, which is an asynchronous function, which has the context and the arguments. so in here let's go ahead and let's get the title using arguments the title dot trim let's go ahead and check if there is no title we're going to throw new error and we're simply going to say title is required and then we're going to check if title length is more than 60 then we're going to throw new error. Title cannot be longer than 60 characters. So that's why we are trimming it so that the user cannot pass empty white space as characters. And let's also get our identity. So identity is await context out get user identity. If there is no identity, let's go ahead and do throw new error unauthorized. All right. And now let's go ahead and simply do const board to be await context database patch. And in here, we're going to pass in the arguments ID. And then we're very simply going to update the title using the arguments and the new title. And you can return the board. That's it. We created our function to update the board. And later, we're going to go back inside of this remove and update function. And we can further enhance the security of this. It will be how you want it to be. So if you want to allow anyone from the organization to delete the board, you can check whether the identity has the matching organization. Or if you want to, you can simply check if the user that created the board is the only one that can edit it and the one that can delete it. Or if you want even further, you can modify so that only admins and the owners can delete that. So we're going to play with that later. For now, I just want a very simple update and remove functionality here. Okay, so now what we have to do is we have to go ahead and create a hook called use rename model. So this one is going to be different. This is going to be a former model, which I want to control in a different way. More specifically, I want to control it using to stand hooks. So let's create a new folder in the root of our application called store. And inside create use rename model.ts. And let's go ahead and import create from to stand. And then let's go ahead and create default values. which are going to be an empty ID and an empty title. And let's create an interface. I rename model is open is going to be a Boolean. Then we're going to have initial values to be a type of default values. We're going to have on open, which will accept the ID, which is a type of string and a title which is a type of string and that's going to return a void and lastly we're going to have an on close function which is going to return a void with no props inside and now let's do export const use rename model to use the create let's pass in the i rename model interface here and let's get the set in the side of this double parenthesis here and let's open and immediately destructure an object inside so we can set the default is open to be false. Let's pass in the on open to get the ID and the title and that's gonna go ahead and call the set function in where we're gonna call is open to be true and set the initial values to ID and the title like that. And then let's go ahead and let's pass in on close to call set again and very simple call the is open to false and initial values to be default values and last thing we have to pass here is the actual initial values to be default values like that so now we have control over our model so now what i want to do is i want to go ahead and create a basic model to rename the board. So for that, let's go inside of components and let's create a new folder called models. So in here, we're gonna store those kinds of models, which are reusable throughout. And let's go ahead and create rename model.psx. If you want, you can also drag and drop confirm model inside, but confirm model is used differently. So you can see that the confirm model is used through these triggers, but rename model is not going to be used like that. So it's going to be controlled with a Cushten store, which we are going to programmatically open depending on how we need that. So, you know, your choice where you want to keep that. So rename model is going to be a client component. And let's go ahead and very quickly import everything we need from components UI dialog. So in here, I'm going to add a dialog, dialogue content, dialogue description, dialogue header, dialogue close, dialogue footer, and dialogue title. And then we're going to go ahead and do export const rename model. And as opposed to how we did it in the confirm model, we are going to heavily rely on our use rename model from store. so inside of here we're going to go ahead and extract is open on closed and initial values and then what we're going to do is we're going to return our dialog and we're going to pass in open to be controlled programmatically by is open and on open change is always going to call on closed so that will programmatically change this one to closed and let's add the dialogue content here, dialogue header, dialogue title, which is going to say edit board title. And then let's go ahead and add a dialogue description, which is going to say enter a new title for this board. And I'm going to leave it at this for now, because here's what we have to do now. We could technically just put this in the root of our layout, for example. The problem is in Next.js, when we use programmatic control of our models using something like Zustand, that can create hydration errors. So I want to show you a bit of a practice that I do. So whenever I have multiple models like this, I create a model.\nprovider so that I can safely add as many as I want and I don't have to repeat a specific piece of code every single time. So we're going to create that as well. Go inside of your providers where you have the convex client provider and add a model provider. This is going to be use client and let's import use effect and use state from react. Let's import our rename model from components models rename model and export const model provider. And in here, we are very simply going to return all of the models we are going to have. So in our case, this is just the rename model, but I'm showing you some practice that I usually do when I have a lot of models here, which all are programmatically controlled. So what I do is I create a little check here is mounted set is mounted. by default false, meaning that the rendering starts in server side, right? But this cannot be shown on server side. Otherwise, it's going to cause a hydration error. So very simply, if I'm not mounted, I'm not going to render any of these models. But only once I get to the client side, will I show them. So how do we know when do we get to the client side? Well, very easy. The use effect can only be called if it's finally come to the rendering on the client side. So if I just create a use effect with an empty dependency array and add set is mounted to true, this will ensure that this component is only ever rendered on server side. Because use client doesn't mean client side rendering. It just means that it's not a server component. And server component, it's not the same thing as server side rendering. Those are two different things. Components which are marked as use client are still server side rendered. They are just not server components. but this ensures that whatever I return here will only be visible completely on the client. And the only reason we do this is because if we don't, it will still work, but you will get some cryptic hydration errors, which are gonna be very hard to debug unless you do this. So the reason I'm doing this inside of a model provider is so that in the future, I can have as many of these types of models as I want, and I can just easily add them here as opposed to adding this logic in every one of my models. So I can just do it once and add all of them here. And then what I'm going to do is I'm going to go back inside of my app layout.tsx. And just below the toaster, I'm going to add model provider. So make sure you add your model provider. Otherwise, your rename model will never be rendered. So make sure you added the model provider in your root layout in the app folder. And now what we can do is we can call the on open function from the use rename model. So let's go ahead back inside of our actions. So actions from our components folder right here. And let's go ahead and add that. So const on open from use rename model. So make sure you import use rename model from store use rename model. And now let's go ahead and very simply find the third option here. So I'm going to go ahead and copy the drop down menu item here. Let's go ahead and let's paste it just below the copy board link. And this one is going to be rename. And the icon is going to be a pencil. So make sure you import the pencil from Lucid React. And on click is very simply going to call an arrow function on open, passing the ID of the board and the title of the board like that. so right now what should happen once I click rename there we go you can see how I programmatically open up my board so you might be asking why did I go through all of this trouble why not just do the same logic as confirm model you can go with that route as well but I did had some issues with the behavior of the actual drop down menu item and drop down itself and how it renders and all the portals basically it all got messed up and I got some unexpected behavior and I didn't want to put that in the tutorial because I couldn't ensure that you wouldn't run into some weird issues with it so what I did was I simply switched to programmatically controlling our model using to stand I actually do that a lot in my tutorials so it might be even more familiar for some of you if you've been watching my tutorials but you are welcome to experiment yourself if you want to use it the same way like I did with the confirm model, you can try and do that. Just remember, you cannot use drop down menu item then. Then you have to use the button component. And if you use the button component, then this drop down menu will not close when you click on it, right? So as I said, you will just run into a bunch of weird little issues. You can see when I click delete here, it doesn't close. The reason it doesn't close is because in here, in the delete, we use a button instead of a drop down menu item. But this is all right for me because the confirm model will actually delete the entire object. So then this will be closed anyway. But for the rename, that's not gonna be true. So I want it to close immediately. So that's just one of the reasons why I switched to programmatically do this instead. Great, so just ensure that you can open your rename model. And then we can go back inside of the rename model here. and let's go ahead and actually write the logic for it. So I'm gonna go ahead here and let me just add my title and set title here to come from use state from React and I'm gonna pass in the initial values. So I need to do that below the username model. My apologies because we need the initial values. So initial values dot title like that. let's add a use effect from react which are going to watch for the changes of initial values dot title so if we open up a new one and we're going to set title here to be the new initial values dot title usually i really don't like doing this in use effect you know the less we use use effect the better but you know i'm going to make an exception for this edge case so just make sure you have use effect and new state imported from react i'm going to move that here to the top all right now that we have that let's go ahead and let's add const on submit here to just be an empty arrow function for now and now let's go ahead just below our dialogue description let's add our form element and let's add our input from dot dot slash ui input so we already have this component. We've used it for our search component. So make sure to add the input component here. And let's go ahead and give it a disabled prop to be false for now. We're going to change that later to our pending state. Let's give it a native required prop. We can also give it a max length of 60 because remember we do that on the back end as well. Let's give it a value of title. Let's give it on change to get the event and call set title of event target value. And a placeholder of board title like that. And then outside of the form itself, let's add a dialogue footer component. My apologies inside of the form like that. So make sure it's inside of the form. we're going to go ahead and add a dialogue close component which will simply say cancel and let's make sure this is as child because we're going to use a native button from chat sorry not native the chat cn button so just make sure you add this input like that and let's go ahead and style it a bit by giving it a type of button which is important so it doesn't submit to the form, and the variant of outline. And then we're going to have a dialog. We don't need another one. We just need a button, which will say save. This one is going to have disabled or false for now, which we're going to change depending, and a type is going to be submit. So now if I try and click on one of this, I should be seeing untitled right here, and let's just give a little space in between these two buttons here. so I believe we can go to directly to the form here and let's go ahead and give it on submit to our on submit which is an empty arrow function and let's give it a class name of space y4 like that I think this should already be better there we go great so we have that and I believe This will now be a controlled component, which is working. Perfect. So what I want to do now is I want to actually implement the onSubmit function. In order to do that, I'm going to go ahead and call my useAPI mutation hook. So hooks useAPI mutation. And I'm going to pass in the API from convex generated API. .board.update like that. From here, I'm going to be able to destructure mutate and pending. and then I'm going to go inside of my onSubmit function. In here, let's go ahead and let's properly mark this as formEvent from React. So import formEvent from React. And it's going to handle an HTML form element. And then in here, we're going to have an event. Did I do this correctly? on submit is a form. Oh, it's not a form event. It's a form event handler. My apologies. Form event handler. And then we can do event, prevent default here. And then let's go ahead and call mutate. And let's pass in the ID to be initial values.id. And title is very simply going to be the new title from the state control. and let's add dot then here and let's call toast from sonor so make sure you import this i'm going to move it here with the global imports so toast.success is going to be board renamed and since we programmatically opened this we also have to programmatically close it so we going to call on close here and let call dot catch here which will very simply just toast the error failed to rename board Like that. So now I believe this should be it. Make sure you have is open on close and initial values from user in a model. Usually when I work with Sustent, I like to do this state state, but I think I don't have to do that. I think I confused the docs somewhere reading that I have to do that for some shallow updates or something. I'm not sure if you know in the comments whether I have to do this. It seems to work just fine without it. I think that's just default, right? I guess. Oh yeah, and we also have the pending here. So let's make sure we use this pending. So I'm going to use the pending to disable the input and to disable the save button. So now if I try and rename this, there we go. It is immediately renamed. If I go ahead and change this, there we go. It is immediately renamed. Let's just confirm in our database that that is also working. Where are my names? There we go. Title, rename, change this, untitled, untitled. So if I go ahead here and rename this one to one, two, three, it's immediately updated right here. Let's go ahead and try the delete one more time. That is working as well. Perfect. So we wrapped up all of the actions here. The copy is working. The rename is working. And our delete is working as well. Perfect. What's left is to create the favorite functionality. And then we can go ahead and implement the additional logic to query by favorites and to query by search. Great, great job. So now let's go ahead and let's implement the favoriting functionalities. Right now, this button does nothing. In fact, it will redirect us to a 404 page. So the first thing I want to do is go inside of convex and go inside of schema right here. And in here, we're going to extend our schema so that we also have a user favorites table. So let's go ahead and create a new table called user favorites. Inside of here, we're going to use define table. and we're going to pass in the organization ID for the favorites to be v.string. We're going to pass in the user ID, which favorited, which is also a v.string. And we're also going to pass in the board ID, which is going to be v.id boards because we have that relation right here above. And now let's go ahead and add a couple of indexes here. So we're going to use a very simple by board index, which is going to use the board ID field. Then we're going to go ahead and add a by user and organization. We're going to go ahead and add the user ID field and the organization ID field. Then we're going to go ahead and have an index of by user and by the board. So let's add the user ID and the board ID. And last one is going to be an index by user board organization. So let's add user ID, board ID and organization ID. So we're going to later check whether we need all of these indexes. I might have gotten ahead of myself here. So basically, the way Convex works is that it kind of allows you to do both SQL and NoSQL kind of schema. So you technically could have made, you know, favorite TED users and make this a v.array of v.string, which would represent something like an array of user ID like that. The problem is that is not exactly scalable, right? And they themselves don't recommend doing that if you have more than a limited number of objects inside of an array. So the proper way to do that is how you would do it in a SQL relational database, which is that you would create a connecting table between the user and the boards. We're going to call this user favorites. We could have also called it user boards. But I think it just makes more sense because these are favorites. And inside we have all the necessary information we need to later check whether a board has been favorited or to load or favorite boards. Great. So now what I want to do is I want to go ahead inside of, well, first let's check if this is okay. So go inside of the terminal where you are running npx convex dev and just confirm that once you save this, it says preparing functions and then convex functions are ready. and you can always go inside of here and let's check our schema again and now we have user favorites here so if I show schema there we go we have user favorites right here perfect so now let's go ahead and let's create some functions to add something to the favorites so I'm going to go inside of convex and I'm going to go inside of an individual board right here so let's go ahead and do the following. Let's go ahead and write export const favorite to be a mutation, which will accept arguments, which are going to be an ID of the board we are trying to favorite, which is a type of VID boards. Then it's going to have an organization ID, which is a type of VID, sorry, V.string. And we're also going to have a handler function, which will have the context and the arguments. So first things first, let's go ahead and let's check if we have the identity. So context, sorry, await context out get user identity. If we don't have identity, we can immediately go ahead and break this function by throwing an error, unauthorized. Now let's go ahead and try and fetch the board we are trying to favor it. So const board is going to be await context.database. And we don't have to use query here because we're only fetching one. So we can use get. And because we established that the ID is of the board's schema, we can just simply pass arguments.id. And that is going to load the board if it exists. So then if there is no board, we can just simply throw new error here, board not found. so now that we have those two checks let's extract the user id so we don't have the right identity subject so it's clearer for us so this is the user id and now let's attempt to fetch an existing favorite so we see so we have to check whether this item which we are trying to favorite is already in the list of our favorite items so const existing favorite is going to be await context.database. And let's go ahead and chain a query of user favorites. Let's use with index, by user, and board organization. Let's get the query. And I'm just going to collapse this in a new line query equals user ID to match user ID equals board ID to match board underscore ID and also organization ID to match arguments organization ID. And let's just not add a comma here all of these are chained events and here's what we're going to do we're going to fetch the first that because it's supposed to be unique do we maybe have dot unique yes i think we can also use dot unique and then this will be just a single element right so if you use collect for example then this will become an array as you can see but since we know we are only expecting a single element like this to exist now you can see that this is just an object of a single element So in the original source code, which I wrote, I used dot first, but now I think it might be better to use dot unique. We're going to see if this is still working. So now let's write if there is an existing favorite. We're going to throw new error here. Board already favorited. And then let's finally go ahead and do await context database insert inside of the user favorites. Let's go ahead and pass the user ID. Let's pass the board ID to be the fetched board underscore ID and organization ID to be arguments dot organization ID. And lastly, we can return the board like that. Or we can do const. Yeah, we can just return the board. It doesn't really matter what we return because we are not going to be redirecting or anything. But yeah, let's just return the board for now. All right, so now we have the functionality to add something to favorite. So now let's go ahead and let's create the opposite one to unfavorite. So I'm gonna copy and paste this entire thing here. And just below that, I'm gonna paste it and I'm gonna call it unfavorite. So it's gonna work the same way. First, let's fetch the identity like that. Let's get the board. Let's get the user ID here. And in here, what we're gonna do is the opposite. So we're going to attempt to fetch an existing favorite. But this time we're going to do this. If we are trying to unfavorite something that doesn't exist in our user favorites query. So we have to use the opposite if clause here by adding an exclamation point. And then instead of this being the error message, the error message is going to be favorited board not found, meaning that you cannot unfavorite something that you didn't favor in the first place. And then instead of insert, we're going to use delete. then that's going to be existing favorite underscore id like that there we go so now we have both favorite and unfavorite functionality here and for the unfavorite we actually don't need to pass the organization id in here so let me just confirm that arguments.organizationid yes, we can actually use the board organization ID. So inside of the unfavorite, since we fetch the board, we know the organization ID for that board right here. And also technically, since board can only exist in a specific organization ID, we actually might not need to use this index So you can make it even simpler by removing this and using by user board Let go ahead and check my schema So we have by user board. Yeah, so I don't think we need by user board ID. But when we are creating one, we do need it. So I'm going to add a little comment here. To do check if org ID needed. So I think we don't need it. I think we're going to use organization. I need to fetch something else. But I'm just going to leave a little comment here in case we have a bug. This is the first place I'm going to look at. So what I did is inside of the unfavorite mutation, I changed it to use a simpler index. So I'm using by user board instead of a by user board and organization ID. because I don't think I need to fetch by that because a board ID can only exist inside of a specific organization ID. So let's go ahead. Let's continue for now and let's just see. Perhaps we can also simplify the original existing favorite then, I might think. We'll see. Okay, let's leave it like this for now. And now what I wanna do is revisit my app folder dashboard underscore components board card right here. And inside of here, where we have our footer, we have to create this on-click functionality, which is going to toggle between favoriting something and unfavoriting something. So first things first, I want to go ahead and I want to import my use API mutation from Hook's use API mutation. And then I want to import API from convex generated API, like that. so let's go ahead and let's extract our options here so use api mutation api board favorite and let's copy and paste this one for unfavorite so we're going to have three of those and let's go ahead and destructure both of those so the first one is going to have a mutate which we're going to change on to be an alias on favorite and it's going to have pending state which we're going rename to pending favorite. This one is also going to have a mutate of unfavorite and a pending state of pending unfavorite like that. And then what we can do is create const toggle favorite. If is favorite, we're going to go ahead and call on unfavorite and pass in the ID. else we're going to go ahead and call on favorite and pass in the id and organization id like that so let's go ahead and use this toggle favorite here and let's just paste it inside of here i think that should be enough and then for the disabled we can use if pending favorite or if pending unfavorite like that. And now here's the thing. So we are hard coding the is favorite. So right now, whenever I click on this, it should give me the create action, right? So here's what I'm gonna do. We don't need a success message for this because it's gonna be immediately shown to the user that something has been favorited later when we implement the actual logic to load that. But I do want to get the errors. So what I'm going to do is add dot catch toast from Sonar error failed to unfavorite. And I'm also going to go ahead and add a catch for unfavorite here. So we know if something goes wrong. So fail to favorite. And make sure you import the toast from Sonar itself. and let's try it out now. So now I believe that my user favorites is completely empty here, but if I click on one of this right here, this redirected me, and user favorites has been successfully created. You can see I have my user ID, I have my organization ID, and I have my board ID, which is linked to my actual board right here with the title rename. Perfect, so this works definitely. and now what I want to do is go inside of I believe that if I try it again I'm going to get an error but let's go ahead quickly inside of the footer right here and I think that we can improve this on click right here let's do the following let's create our own const handle click to get an event, which is a type of react.mouse event, HTML button element, and mouse event. So let me just see if I imported any of those. No. All right. So HTML, nothing. Okay, all of this should just be existing in the scope. And what I'm going to do is event stop propagation and event prevent default. and then I'm gonna call on click and then use this handle click instead of direct on click here when we favor it something. So now when I click on the star icon, I should not be redirected, but there we go. I got an error, failed to favorite. Why? Because this is already in my database of user favorites. So I cannot favorite again something that has already been favorited. But if I click on this, let's just wait for this to hide. If I click on another one, then I have no errors here and I have a new record inside of my database but if I try again I get an error because that one is already favorited so what I want to do now is I want to modify my main board my main convex boards function right here so that in the end I also include a field is favorited for this logged in user also if this is not working for you Here's what I recommend that you do. So inside of app, where were we? We were in the board card index. So if you want to, you can use the alternative. You can use const handle favorite and use mutation from convex react and pass in API board favorite. and then when you call handle favorite here, you're gonna have, oh, you can see that you have some type errors, right? So you would have to write this as ID as type of ID boards like that, for example. So if you're having any errors, it could be due to invalid typings. So you can always check that here. Yeah, so perhaps we are losing a lot with my custom use API mutation, but I really like the fact that we have pending here. So yeah, you can always fall back to using useMutation from ConvexReact if you are confused. And then you can check your types more thoroughly. Great, so let's go ahead now and let's go inside of the boards here. And what I want to do is I want to query them so that all of them have a favorite relation. So after I load my boards, what I'm going to do is I'm going to write const boards with favorite relation. And that's going to be boards.map. We're going to get the individual board. And then I'm going to open a function and I'm going to return context.database.query user favorites with index by user board. Let's get the query. I'm going to send this to new line. And I'm going to call query.equals user ID to be identity.subject. And .equals board ID to be the current board that we are iterating over ID. And let's go ahead and get unique. and then if we have a favorite let's go ahead and return an object of this exact board but also is favorite which is simply going to be a boolean based on the fact that whether we can load the user favorite or not like that great so after we've added that then what we can do is write const boards with favorite boolean is going to be promise.all and we can pass in boards with favorite relation inside like that so let me see if i can expand this basically in one line and make sure you return the new boards with favorite boolean right here so now if you go back inside of board list component right here. I don't know why it's collapsed. The data now should have the is favorite Boolean. You can see that my data has is favorite Boolean in here. So what I can do is I can change this from is favorite false to where we are either reading overall board cards in use board and add is favorite like that. And let's see if that is working, there we go. So now if I click again, I unfavorited it. If I click here, I unfavorited it again and you can see how I'm no longer getting any errors because now our function knows when it should favorite something and when it should unfavorate something. Great, great job. So you wrap that up and one more time, you know, if you're having trouble with this, I would highly recommend that you go ahead and use the native use query from convex slash react. I thought this would be maybe a fun exercise, but I can see how with this any typings, some of you might be losing, you know, the idea of what actually needs to be passed there. But yeah, you can see that obviously this is still working, but if you're getting confused, you can go ahead and write it alternatively. So I can do it just for this, for a little exercise. I'm going to do that now. You can speed up the part if you don't want to, this is how you would write that. So I'm going to call const handle favorite to be use mutation API board favorite And then const handle unfavorite is going to be useMutation API word unfavorite I getting the useMutation from convex slash react And then in here, I would very simply call the handle unfavorite. And in here, I will do handle favorite. And then in here, I'm going to go ahead and map this as ID, which we can import, I believe. Let me just check if we can import it. I should be able to import this ID boards. Oh, my apologies. So ID as the type ID, which you can import from at slash convex underscore generated. So let's go back here. There we go. So import ID from at slash convex underscore generated data model. And then when you're using that ID specifically, you can specify even further that this is supposed to be an ID. of boards. But the thing is, even though TypeScript throws an error, you can see that this as doesn't change what we actually send here. So that's why our solution with use API mutation also works just fine, right? So let's try it out. And this, if this will work just fine, there we go. You can see how this is working, but you can also see that my state is not disabled while I'm doing that, because I have to now manually write is pending here. So I'm just going to revert it back to the old one. But I just wanted to show you an alternative way if you're having issues with this, or if you simply want to practice, you know, TypeScript with Convex here. All right, I think I have all the imports I need here. I think all of this is working for me. Let me just confirm one more time so I can wrap up this chapter. Yes, nicely. I can add to favorite and I can remove from favorites. Perfect. What we're going to do next is wrap this entire thing up by enabling the search and querying by actually favorited items. And now let's go ahead and wrap up our search and our favorite query here. But just before we do that, I want to bring your attention to the fact that if I favorite a board and then have that inside of my user favorites, and then if I go ahead and delete a board, what will happen is that I have an existing user favorite, but I don't have a proper relation with the board. And that will in exchange cause an error. So if you've deleted a board while working on this user favorites, here's what you can do. So every time inside of your database here, you can always delete all documents, right? So you can just select, select all and delete all documents. You can always do that quite easily inside of here. There we go, like that. So let's make everything empty for now. Let's go ahead inside of our convex board.ts and let's find our remove function and in here we actually have to do later check to delete favorite relation as well. So that's exactly what we're going to do. We're going to go ahead here now and we're going to write const existing favorite. And just before we do that, we can also get the user ID to be identity.subject. So then inside of here, let's go ahead and write await context.database query user favorites like that with index. and we're going to go ahead and use by user board. Let me just quickly check the schema. So by user board exists. All right. So by user board, then we're going to go ahead and get the query. then we can do query dot equals user id to be user id and equals board id to be the arguments dot id like that and let's go ahead and get unique from this combination and then i'm gonna write if existing favorite exists let's make sure to remove that relation. So await context.database.delete existing favorite underscore ID like that. So let's try that out now. So I'm going to go ahead and I'm going to create a new board here. So let me just confirm inside of here in my boards, I have a new board and I don't have any user favorites here. Once I add this to my favorites, I now have a user favorite and I have one board. And if I go ahead and delete this board, I should have empty user favorites and I do zero documents and zero boards. So we successfully fixed that issue. Great. Now that we have that resolved, let's go ahead and let's try one more thing in our favorite function i think we can speed up this query for existing favorite i don't think we need to fetch by organization so we can remove organization from here and simply use by user board and we can also safely then remove this comment here to do check if organization id is needed i don't think we need that if you notice that we do need it, you can bring it back, but I think that that query is not needed for us. This all seems to be in sync. If I remove this, both of this get removed. Perfect. So all of this seems to be working exactly as it should. Let me just try this. Perfect. Okay, so now let's go ahead And let's actually implement that when we search for something, we actually query by our search index, which we have inside of our schema. So we have a search index here for the search title. So in order to do that, we have to go inside of our convex boards right here. And we have to allow other arguments. We have to allow arguments other than organization ID. So that's going to be search, which is going to be optional and a type of string like that. Perfect. So now let's go ahead and let's do the following here. We're going to add a constant for our search. So const title is going to be arguments.search as string. Then we're going to go ahead and write let boards to be an empty array initially. And we're going to remove the constant from here. and this is what we're going to do if we have the title we're going to write uh to do query with search index else we're going to go ahead and query our boards as we usually would like this so let's go ahead now and let's modify the boards here to now be uh await context dot database We're going to go ahead and query the boards, but we're going to use with search index this time. Search title. We're going to get the query. And then we're going to do query.search title and title. But we are also only going to search inside of our organization ID, which comes from the arguments as well. And let's do .collect. There we go. So now this should work just fine. The only thing that we are not passing is the search. So let's find where we have to pass that. Let's go inside of app dashboard components board list. And do I have the queries here? I do have all the queries here. So I think that we are pretty safe to simply write search query.search besides organization ID. So let's try that out now. I'm going to go ahead and remove everything here. And if I write untitled, there we go. You can see that I have untitled. If I type something completely different, we don't have that. Perfect. So let me try renaming this to, for example, specific, just specific. If I type untitled, it no longer exists. If I type specific, it exists. Perfect. Our search is now officially working. So now what I want to do is I want to do the same thing, but allow it to be queried for favorites as well. So here's what we're going to do. Let's go ahead. And while we are inside of this board list, instead of passing this individually, we're just going to spread the query like that. And then let's go back inside of boards.ts. And let's also add favorites to be v.optional, and let's go ahead and mark this as v.any, like this. Actually, I think we can make it a string. Let me just check inside of board list. This is a type of string, so let's make this a string then. Yes. All right, and now we have to do a similar thing, so let's go ahead and go just Before we check the identity, let's do if args.favorites. In that case, we're going to do const favorited boards are going to be await context.database. Query user favorites. We're going to use with index here. By user and by organization that that user is in. let's get the query and let's do query equals user id to be identity.subject and equals organization id to be arguments.organization id and let's go ahead and order all of them by descending so the latest added to the favorite list and let's collect all of them so this way we're going to get all of the favorited boards and now let's go ahead and get all the IDs by using favoritedboards.map, and let's go ahead and extract board ID, sorry, B underscore ID out of every one of those boards. And now what we can do is we can install convex helpers, so let's go ahead and do that.\nSo I'm going to go ahead and shut down convex for now. And let's do npm install convex dash helpers like that. Let's wait a second for this to install. And once it is ready, we're going to go ahead and do npm npx convex dev again. So just make sure you have npm run dev running in one terminal and in another npx convex dev. and in here I'm going to go ahead now and I'm going to import get all or throw from convex helpers slash server slash relationships like that so we don't have to write any complex queries here instead what we can do is const boards to be await get all or throw context.database and simply passing the IDs as the second argument. And then we can do boards.map, get the individual board, and let's go ahead and immediately return and spread the existing board. And simply map is favorite to true. We don't need to do any calculation here. We know it's going to be true because we are only loading the boards by the favorited boards IDs right here. And I made a mistake here. So this will actually not give us the result we want. So the list of IDs that I wanted to make was the list of relationships inside of our schema. You can see how I actually have an error here. So this tells me that this doesn't exist. That's because I'm supposed to use board ID here. And there we go. Once I change this to board ID, this will actually load the boards. and now you can see that this is favorite will still exist. So let's try it out now. So if I click on favorite boards, it says no favorite boards. Try favoriting a board. If I add this to my favorite boards and click here, there we go. If I remove it from here, it no longer exists. Perfect. So let's try this with a couple of more elements here. So I'm going to randomly add a couple of them. There we go. So only they appear here. Perfect. Let's go ahead and try searching for something that should bring us back to the theme boards. Let's be more specific. That still seems to be working just fine. Perfect. Amazing, amazing job. You just added a complete search querying and everything that we need here. And what we are finally ready to do next is build this actual screen, which will happen when we click on that. But just before we do that, we can already prepare ourselves by doing this. So let's find everywhere where we use API board create. So we have that in two files, in empty boards component, in app dashboard underscore components, and the new board button. So let's go in empty boards first. Let's see where it's located. App folder dashboard components, empty board. So we can now actually do this redirect right here, right? we know how it's going to look like. So const router, use router from next navigation. I'm just going to move that right here. And what I'm going to do now is do router.push board. And I'm going to pass in that specific ID like that. And I can remove the to-do comment. And the other one which we had, let's see if we had another one, new board button. So this one is located in the same place, I believe. So app dashboard underscore components, new board button. So in here, we have the same thing. Let's go ahead and get our router here. Const router, use router from next navigation. So just ensure you're using next navigation here. And let's just try that out. So now every time that we create a new board, we should get redirected to the currently 404 page, which in a next chapter is going to start looking like an actual board. So let me just go ahead and remove all of my elements here. So we can confirm that it is actually working from that empty screen as well. We confirmed that the button works. But how about this one? Same thing. it works. Perfect. Great, great job. So let's go ahead and let's create this 404 page, which appears when we click on something. So I'm going to create that route outside of our dashboard route group. So I don't want it to have this layout with the sidebar, with the organizational sidebar, or the navbar. Instead, I want it to be a completely blank slate. So I'm going to go ahead and just create a board folder in the app folder outside of the dashboard. And then in here, I'm going to go ahead and create a board ID dynamic route. So this way, this will be part of the URL, which is dynamic. And using that, I will be able to load the board information and its storage and history of all the drawings we're going to have inside. Now inside, it's important that you create a convention file page.tsx. And let's go ahead and name this board ID page. Let's return a div, board ID page. And important to export default board ID page like this. And now if you go ahead and click on this, you should no longer be having a 404. Instead, it should say board ID page like that. Now go ahead inside of this board ID and create a new folder underscore components. This is where we're going to put some reusable components. And let's start with the first one, canvas.tsx. We're going to mark this as use client because it's going to be very interactive. and let's just export const the canvas component for now. Let's return a div saying canvas. Now let's go back inside of page.tsx here and all I'm going to do is render that canvas component from our underscore components. So what you should see is just the text saying canvas. Now let's go ahead inside of the canvas here and let's replace this div with a main element and let's go ahead and give this a class name. The class name is going to be age full, width full, relative, bg neutral 100 and touch none. So by giving it this bg neutral 100, it should darken the background a little bit. If you want a better effect, you can go to 500, but keep it at 100. It will look good because our floating elements will be completely white and have a little shadow. So it's going to look okay. Now let's go ahead and let's create another component, which is going to be called info.tsx. So let's go ahead and export const info here. And let's just return a div saying info. Now let's go back inside of the canvas. And we are very simply going to import that info component from dot slash info. And now let's go ahead and actually give this info component a class name of absolute top2, left2, bgwhite, rounded, md, px1.5, height of 12, flex, items center, and shadow medium. and there we go you can now see our info component right here in this corner on the right side so I'm going to write this to do information about the board there we go so you can see how it's going to look when it has a bit more information around it so in here we're going to have a logo to click on so we can go back to all boards and then we're going to have the current board name so that we can rename it. And in here, we're going to have additional options in our reusable actions component to delete the board, copy the board ID so we can share it and stuff like that. So this will be floating right here in a fixed position. Great. Now, besides info, let's go ahead and create another reusable component inside of underscore components, which is going to be called participants. dot tsx. So let's go ahead inside of participants here. And let's very simply export const participants, which is going to be very similar. So let's go back inside of canvas and below info, add the participants. From dot slash participants. And now inside of here, we're going to go ahead and write class name. absolute height 12 top 2 right 2 background white rounded md padding of 3 flex items center and shadow medium and in here i'm going to write list of users there we go so you can see how this is going to look like in here we're going to have avatars of the all users which are currently inside of this board and their borders are going to have respective colors to their cursor which will appear right here alongside their name when they are active and the last one we're going to create is right here and that's going to be our toolbar so let's go ahead inside and create the another component here called toolbar.tsx let's export const toolbar here toolbar like this and let's go back inside of the canvas component and let's render the toolbar from dot slash toolbar there we go so you should have the info the toolbar and the participants let's go inside of the toolbar now and let's go ahead and style it as well so i'm going to give this div a class name of absolute top 50% so it's at half of my screen then I'm going to give it minus translate minus y again 50% and then let's give it a left to a flex flex call gap y4 and then inside I'm gonna create a div with a class name of VG white rounded MD like this padding 1.5 flex gap Y1 flex call items center and shadow medium And in here I going to write for example we can do divs pencil Then this one is going to be square. Then this one is going to be a circle. right so just so you get the idea this one will be for example uh i don't know what else do we have let's just write ellipsis again all right so that's going to be like our toolbar here but besides these elements we're also going to have a one additional uh kind of part of the toolbar, which is separated from this div, which has BG white. So outside of that div, which is currently encapsulating this mock items, go ahead and create another div, which is going to have a very similar style. So in here, we're going to have a class name of BG white rounded MD padding 1.5 flex flex dash column items center and shadow medium like this and then inside let's create a div and write undo and let's do one for redo like that there we go so you can now kind of see the layout which we are going to have in this corner right here we're going to have information about the board In this corner right here, we're going to have a list of active users, which is going to only show a maximum of three users. And above that, it's going to just display a number of how many users are there in total. And in here, we're going to have our toolbar to select the pencil, square, circle, or whatever else we're going to have. Below that, we're going to have our undo and redo buttons right here. And the rest is going to be the logic which we're going to work inside of here. Great. So just before we wrap this up, let's just go ahead back to localhost 3000 here. Let's confirm that this is surely working. When I create a new board, I'm redirected here. Perfect. Let's go back and let me just delete this to ensure this is working. When I go from here as well, that is working. One thing that I'm interested in is that if I favorite this board and go into my favorite boards, it's right here. All right. But if I switch on organization, then it's not here. Perfect. Let me just confirm that in here I should have a new one. So this is new org. So if I add this to favorites, that is only inside of this organization. If I go back to the other one, then it's untitled. Perfect. Everything is working just as we expect. And now let's just go ahead inside of page.tsx here and let's just prepare our props. So go inside of let me just close everything go inside of app folder board board id page.tsx and let's go ahead and create an interface board id page props to have params which are board id which is a type of string and in here we're going to have board id page props and in here we can extract our params. And let's also prepare the canvas to accept the board ID prop from params board ID. So we can go back inside of underscore components, canvas right here. And let's just go ahead and quickly create our canvas props. So interface canvas props is going to have a board ID, which is a type of string. And let's go ahead and destructure that. And now we have our board ID and we're going to go ahead and use it later in the info component right here. Great. So we created a nice little skeleton layout for our application. And now in the next chapter, we're going to go ahead and actually connect to live blocks so that we can actually authenticate the user attempting to enter this board because we're only going to allow users from the same organization to look at a board. We're not going to allow anyone to just join and we're going to make sure that that logic is working. Great, great job. So now let's go ahead and let's connect to LiveBlocks. So use the link in the description or go to liveblocks.io and go ahead and create an account. Once you're inside, you're going to have two default projects, the development and production. So for this one, I'm going to click on development right here. And then inside of my API keys right here section, I can see my public key, which I'm going to need to create a LiveBlocks client. So now let's go ahead and let's go inside of our terminal so we can set up LiveBlocks properly. So for this step, I'm going to shut down all of my terminals. I don't want anything running because I will just be installing LiveBlocks. So let's go ahead and run npm install at liveblocks slash client. And after that, let's go ahead and install liveblocks slash react. So let's wait a second for this to finish. And then liveblocks slash react. After this has been installed, we're going to be able to run an npx command to initialize the liveblocks.config.ts file. So for that, run npx create liveblocks-app at latest. And then let's write dash dash init dash dash framework. And then react. So let me zoom out and try and show you this in one line. So npx create liveblocks-app at latest dash dash init dash dash framework react. All right. And let's run enter. and that should set up our LiveBlocks config. So just confirm the newest version if you get this prompt. Would you like to use our React Suspense hooks? You can select yes for that. Are you using TypeScript? Yes for that as well. And there we go. LiveBlocks config TS has been generated. So now you should be able to find this file right here, liveblocks.config.ts. And as you can see right here, We have an error because we have TypeScript added here and it's expecting an actual public API key. So let's go ahead for now and remove the auth endpoint. Let's remove the throttle and let's just uncomment the public API key. And then we can use this one from our API key settings here. So just click to copy this. And I'm going to paste it here like that. So it should be in one line, of course, like this. There we go. Great. And now let's go ahead and let's create a reusable room component, which will wrap our existing canvas. So for that, I'm going to go inside of components here, and I'm going to create a file, a room.tsx. Let's mark this as use client. And let's go ahead and import React node from React. Let's import room provider from at slash liveblocks.config. And let's go ahead and import client side suspense from liveblocks.react. I'm just going to keep this two separate because this is our alias path to a file, right? Which we have right here, liveblocks.config. And in here, you can see everything that is exporting like a room provider, which we are importing here. And this is an NPM package. So that's why I like to separate these imports, because this is an alias to my file and this is an npm package. And now let's just export const room. Let's go ahead and give it a type of children to be React node. Children like this. And then in here, very simply, we're going to return a room provider. and in here we're not just going to accept the children we are also going to accept an actual room id so let's just go ahead and add room id prop as well and define it here to be a string and then we can pass in the id to be room id right here and initial presence is just going to be an empty object for now and then inside we're going to add client side suspense and we're simply going to go ahead and render the children. And let's add a fallback here for now to just be a div loading like that. Perfect. So I just want to be consistent with the way I'm creating props. So I'm going to separate this like I always do, room props like that. and then I will simply use room props here. You don't have to do it. I just like to be consistent with my types. And now let's go ahead back inside of app board, board ID, page.tsx right here. And now we're gonna go ahead and import room, not from LiveBlocks client, but from components room like this. And now we can wrap our canvas in a room. just like this. And let's go ahead and pass in room ID to be params board ID, like that. So now, if you take a look in your live kit here, rooms, I'm not sure if we're gonna have any rooms. There we go, we have no rooms yet. But I think that just by refreshing this page after we run npm run dev, the fact that we wrapped our canvas in a room component, that is going to trigger a new room. So let's do npm run dev. Let's open a new terminal for npx convex dev like that. And let's go directly to localhost 3000 first. So we ensure that our authentication and everything is working. So I'm just going to refresh one more time to make sure this is synchronized. There we go. So once I click here and once I refresh here, there we go. You can see how I have a new room ID which has the same ID as my board ID from Convex. So this way we synchronize the live blocks with our Convex database in that way by using the same unique ID so we know which LiveBlocks room relates to which canvas board Perfect So if you go ahead and click on some other boards that you have, that's gonna go ahead and create a new LiveBlocks room for you. So right now I only have one room, but if I go to some other organization right here, there we go. Now I have two rooms and it uses the ID of that board. you can see that the ID is the same as here. Perfect. So now that we have that, I want to go ahead and extend this room component a bit. Since it's reusable, I want to make sure that the fallback that I'm passing, it's also reusable. So this is what I'm going to do. I'm going to go ahead and add all back here. And I'm going to attempt to use this non-nullable react node or null like that. And then I'm going to extract the fallback here. And I'm going to pass the fallback like this. All right, no errors here. But now my page should have an error. And in here instead, now I can pass a div, for example, loading like that. And now if I refresh here, this should still work just fine. Great. What I want to do now is I want to create an actual loading for my board. So inside of here, I'm going to go ahead and create a canvas loading element. So let's go ahead and create canvas loading.tsx. Or maybe we can just name it loading. Let's name it loading. and basically what we're going to do here is we're going to import skeleton from components UI skeleton and we're going to import loader from lucid react and then let's go ahead and export const loading and let's return a main element and I want it to be the same as my canvas, right? So we can copy this specifically like this and let's see if we have to change anything. So we need a relative, we need BG neutral, but let's also do this. Let's add flex items center and justify center. So the reason I'm adding this is because I'm gonna have a loader, which I've imported in the middle of the screen. So class name height six with six text muted foreground and animate spin. And let me just see if I can already use this. So inside of my page.tsx, I'm going to remove this and use loading from dot slash components loading. And I think that if you have a slow connection, you might see a little loader for a second. The thing is that there's nothing loading from live blocks at the moment. So perhaps we are not even able to see it right now. So here's what I want to do. Let's just manually return loading. Just so we can see what we're building. There we go. So now we can see our loading element here. So I want to create that room loading component first. And let's go ahead and continue developing it. So now I want to create like little skeletons or placeholders for all of those floating elements, which we're going to have, which are the info here, the participants and the toolbar here. So let's go ahead and let me open up the info component. So what we can technically do is write info dot skeleton to be function info skeleton. And then we can just return this, right? and it can actually be a self-closing tag, I believe, because it doesn't matter what's inside. It's going to be empty either way. And you can use the skeleton component if you want to. Let's actually use the skeleton component, yeah. So I'm going to expand this. My bad. Let's use the skeleton inside from components UI skeleton like this. So we are actually going to import that here in the info. let's give the skeleton a class name of hfull, widthfull and bgmuted 400 like that. And let's go ahead and give the outer div some changes in the class name. So we are going to have to manually give it a specific width. So let's go ahead and do that. Let's give it a width of fixed 300 pixels like that. And let's see how this looks for a skeleton. So if I go back to my loading and below the loader, if I add info from dot slash info dot skeleton, and I can remove this skeleton from my loading. There we go. I think this looks okay, right? Because if you add, you can change the skeleton to be more visible. For example, you can remove this BG muted, but then it just looks weird because the background itself is already dark. So I think you can just increase this and then it looks better because it's just a blank space indicating something is loading here and we have this loader here. So I think it's kind of clear that it's loading that information above. And the same way we created this info skeleton here, we're now going to create the participants skeleton as well. So let's write participants dot skeleton is going to be function participants skeleton. and we're going to go ahead and copy and paste this like this and let's just add a skeleton component from components ui skeleton let's give it a class name h full and width full and bg muted 400 and let's give this one a fixed width of let's do uh i don't know 100 pixels like that and let's try it out so let's go back inside of loading here and below that add participants that skeleton there we go we now have a loading for our participants here and the last one we're going to do is for our toolbar. So let's go at the bottom and write toolbar skeleton function toolbar skeleton. And in here, we're going to go ahead and add first, let's do this like that. And let's add a skeleton component inside from components UI skeleton. So I just copied this outer div, right? And instead of creating, you know, so in this toolbar, we have like the outer div, which just positions the elements. And then inside we have this BG white. So how about I combine the two? So what I can do right here is just add BG white. And then I can also fix the height of this to be 360 pixels, because that's how much elements I expect inside later. And let's also fix the width to be 52 pixels like that. And let's give this a class name, height full, width full, and BG muted 400. Oh, BG muted 400 doesn't exist at all. That's why it looks white. Okay, so we're going to refactor this. Let's just take a look at how this looks now. So toolbar.skeleton. so make sure you have info toolbar and skeleton and let's refresh this uh okay so this looks good this looks good and the toolbar is missing a shadow so let's go inside of the toolbar here and just add a shadow md okay and we also need a rounded md i believe there we go now it looks just like the others and i think we actually don't need the skeleton inside so if i remove the skeleton yeah nothing has changed so we don't need a skeleton and all of this can be self-closing devs so i just fixed that in the toolbar skeleton so we don't need nothing inside we can be a self closing tag like this and you can remove the unnecessary skeleton now i'm going to go inside of info and do the same thing we don't need the skeleton import and we don't need this inside we don't need the ending tag because this can just be a self-closing tag and let me just refresh to confirm this is still working this is still working also yeah when you're modifying code inside of this info skeleton like this uh don't rely on hot reload too much manually reload to see your changes because i think that if i just change this to bg red 500 okay now it works of course when i try to demonstrate it it works but while i was developing uh hot reload gets kind of confused with this kind of experts so if you're not seeing your changes immediately just refresh manually. All right. So we fixed that for the info. We didn't need that skeleton component. And the same thing is true for the participants component right here. We don't need the skeleton import. We don't need to use it here. And we don't need, we can just use the self-closing tag like this. There we go. So now we have a nice little skeleton page, our suspense for actually loading the canvas room connected to live blocks. So why are we even going to need the loading for that? We are going to need the loading because we're going to in this room component later. So we can now remove this return. Right. We can now just let it go back to it's going to use that loading as a fallback. We've now know that it works. We know how it looks like. Otherwise, it would have been very hard to develop it. So the reason the loading is later going to be needed is because inside of our liveblocks.config.ts in the next chapter, We're going to go ahead and modify this create client to also have an API endpoint, which will authenticate the user which is allowed or not allowed to visit this board. So we successfully connected to our live blocks and we are now ready to do that. Great, great job. Now let's go ahead and let's authenticate each user trying to access our room. In order to do that, the first thing I want to create is a way to fetch the board using the ID so we can see whether the user has permission to enter that board. So let's go ahead inside of convex, inside of board.fes. Now I going to go all the way to the top here Actually let go all the way to the bottom And let export const get to be a query from generated server So import it from the same place where you imported the mutation. And this query is very simply going to have arguments to accept the ID. And then that's going to be v.id boards. And then we're going to have a simple handler, which is an asynchronous function, which has the context and the arguments. And all this is going to do is get the board using context database, get arguments ID and return the board. And no, we are not going to add any authentication here because we're going to use this only to access it through our route handler, which we're going to create now. And in there, we're going to have separate authentication. So you're going to see in a second why we need to do it this way. So for now, just make sure that you have a very simple get query inside of the convex function here. What we're going to do now is we're going to follow the instructions on setting up authentication for live blocks. So in here in the documentation, you have the authentication column. You can use their authentication or you can build your own authentication. So I'm going to do that using the access token and select next JS. So let's go ahead and install this package, npm install live blocks node. I'm going to go ahead inside of my terminal here. And I will shut down convex dev for now and just run npm install at live blocks slash node. And then we can do npx convex dev again, like this. Let me just shut all of this down. and then what we're going to have to do is we're going to have to create an endpoint live blocks out route.ts. So let's go ahead and do that. So I'm going to go inside of the app folder and I'm going to create a new folder called API. And inside of here, I'm going to go ahead and create a new folder, which is also going to be the name of our path live blocks dash out and then inside route.ts. So this is the exact same convention as creating client side pages. But instead of page.tsx, we have route.ts. So let's go ahead now and let's import live blocks from at live blocks slash node, which we've just installed. And then in here, we're going to initialize live blocks using new live blocks. And then we have to pass in the secret this time. So inside of our live blocks config, we use this public key. But in here, we need to use a secret. And you can see that inside of this configuration right here, it uses a secret key. So you need to go back inside of your project here inside of the API keys. And this time reveal your secret key and copy it. So we have to use the secret key here on the backend. And let's paste the secret key here like that. Then what we have to do is we have to initialize convex so that we can use convex inside of our route handlers. And there is a way to do that. So let's go ahead and import convex HTTP client from convex slash browser. And let's also import API from convex generated API. And then let's define a convex instance here using new convex HTTP client process.environment.next underscore public underscore convex underscore URL like that. So let me just collapse this so you can see how this is supposed to look like. And of course, confirm in your environment local that you have next public's convex URL. It's supposed to match here and you can add an exclamation point at the end to get rid of the error. So you should now have an instance of convex and live blocks inside of your Next.js route handlers. So in here, what we can now do is export asynchronous function post, which has the request, which is like request. And what we can do here is we can use clerk because we have a middleware file, which means that we've used, we've protected our entire backend and frontend with clerk. So we can import some useful utils here, like out from clerk next JS and current user from clerk next JS. So let's add both of them inside of this post request here. So I'm going to add const authorization to be await out. And I'm going to have const user to be await current user. Like that. If we don't have authorization or if we don't have the user, in that case, we're going to return new response, unauthorized, and we're going to pass in a status of 403. And then let's go ahead and extract the room that we are trying to join from await request.json. So this is where LiveBlocks is going to send the room information in. So if you're wondering where are we going to fire this live blocks out throughout, we're going to do that by modifying our live blocks config. So inside of this create client, we're going to change this to target to this API route every time the room component loads. So that's how I know that this room is going to be inside of my request.json. And of course, because that's how it's written right here in the documentation. So we get the room and then let's go ahead and get the board using that room. So const board is going to be await convex.query API board get. And we are very simply going to pass the ID as the room itself. And now let's go ahead and check if board question mark organization ID is not equal to authorization of the current user dot organization ID like this. Return new response unauthorized. Like this. and then let's go ahead and let's actually create the user info, which we're going to pass back to the client. So const user info is going to have a name of user first name, and it's also going to have a picture of user image URL like this. And then let's go ahead and create a session, which is going to be liveblocks.prepare session and pass in the user.id as the first argument. And for the second argument, open an object and pass in user info to be user info, or you can use a shorthand like this. And now we're going to have this two errors here, which you can easily fix by adding exclamation points at the end, or you can use aliases like anonymous, for example, if the name isn't available. So you can choose one of those. So either do it like this or add an exclamation point, or maybe a teammate. And now that we have this, let's check if we have a room. In that case, let's go ahead and do session dot allow room session full access. Let's go ahead and destructure status and the body from await session authorize and return new response body and status. And to make it easier for ourselves to understand what's going on here, let's do the following. So once we hit the post route, I'm going to go ahead and add a console log here. And I'm going to say out info. And I'm going to open an object as the second argument for my authorization and for my user like that. So when this endpoint hits, I'm going to see in the terminal whether I have authorization. Then in here, I'm going to go ahead and I'm going to check if I have the room. I'm going to check if I have the board. I'm going to check the board organization ID using board question mark organization ID. And I'm going to check user organization ID, which is going to be authorization dot organization ID. You don't have to do this, but I'm just giving you some tips on how you can debug this to see if something goes wrong or why it works in the first place. And the last thing we can do, for example, is just check console log user info here. I like to add this in an object is easier for me to see that way. Great. And the last thing here we're going to do is console log. Let's do it just before we return the session. Let's add status and body allowed like that. So I just added a couple of console logs. So it's easier for us to follow the flow of what's going on inside of this API route. So now we're going to go back inside of liveblocks.config.ts. and we're going to remove the public API key. And instead, we're going to add out endpoint. And that's going to go to slash API slash live blocks dash out. So first things first, confirm that this slash API live blocks dash out matches your new API structure. App folder API live blocks dash out. Make sure you don't have any typos. Otherwise, this is going to be a 404 route. so first things first let's check if we have this inside of our network tab at all so I'm going to go ahead and search for live blocks slash dash out to confirm that that's going to be loaded here once I refresh let's take a look and there we go you can see how it's pending and it received back a token perfect and you can see how we have the payload with our room id and now because of all of those console logs, which we've added, you can go inside of your server terminal. And in here, where you're running npm run dev, you're going to see all of that useful information. So let's take a look at it from the start. Let me just go ahead and find where this is starting. All right. So first, I have the out info, you can see that I have the authorization, that is great. And I have the user model, that is great. And let's take a look at my authorization, which is important. So your authorization object should have the organization ID. I also have the organization role. So in case you don't have organization ID or organization role, which you can also confirm a bit lower here, I think we created a custom object.\nLet me just scroll a bit down. There we go. You can see that my board organization ID matches my user organization ID. So that's why I allow this user to visit this board. So in case for some reason you don't have that, you can always visit clerk. And inside of your JVT token, just confirm that you have the organization ID passed. And I also have the organization role passed, for example. Even though I'm not exactly sure that this JVT template relates to what I do in my backend. I'm not entirely sure. Great, but basically, if in your console log right here, you can see this allowed, you can see the status 200. And inside of here, of course, you don't have any errors. You should be able to see this completely. So now what I'm going to do is I'm going to copy the URL from here. and I'm going to go ahead and create a completely new user and see if I can visit that board then. All right, so I'm in a completely new account and if I paste that board URL, let's go ahead and see what happens. It looks like I can still access this, but we can see that authentication did fail. So our API route is working and our authentication is working, but I may be doing something wrong. So we do have unauthorized, but for some reason, we still have access to this right here. So I'm going to go ahead and debug this a bit. So let's try this. So find the inside of your API live blocks out where we do return new response unauthorized. Try and also give it a status of 403. Like that. And let's see if that's going to change anything. If I refresh here, let's see anything new from the error unauthorized 403. So it definitely is working. Oh, and the web, and it seems like the web socket server has closed. I think this is actually okay. The reason we can see still see these things is because they are hard coded, right? So usually what we're going to do next is we're going to attempt to load the information of the presence of the users, but none of that is going to work because we are not authorized. So I think that for now, this is actually completely okay. I'm going to research later if there is a way to redirect the user if we are unauthorized, but for now, I think this should be just fine. Great. So we just wrapped up and added the authorization to our project. And let's go ahead and see what happens. I think we can confirm this even further by then going inside of the client here. So we did this. And let's go ahead now. And we have to extend our liveblocks.config.ts. So our info has the name and the picture, remember? Because inside of our router TS here, let me just remove all of these console logs. I don't need them anymore. So we confirm that this works. Yeah, so in this user info, we have name and picture. So we need to add that to our TypeScript inside of LiveBlocks. So let's quickly go inside of liveblocks.config.ts right here, and let's find user meta right here. So I'm going to remove these comments inside. I'm going to add an optional ID to be a type of string. And I'm going to get info to be an object, which will have name, which is an optional string and picture, which is an optional string as well, like this. Great. And let's see if there is a way for us to see if we can render the authenticated user. All right. So we, yes, we did this. That is fine. Oh, and then we can use use self hook. So let's try that out very quickly. So we have to use that somewhere inside of our canvas. Let me do inside of a room, my apologies. So inside of this. So let's go inside of canvas here. And let's just, this is already marked as use client. Perfect. So let's go ahead and import use self from live blocks config. and let's see if we can get that here. So what does the documentation say? We should be able to export name from useSelf. Is it? Oh, useSelf, then we can get me, me.info. Like that. Name does not exist. Let me just try and write X. how about we just mark this as info in general and console log info. So now let's check this out. If I go here, so right now I'm in a place where I shouldn't be able to see anything and it looks like it's working. You can see how my board is just infinitely loading because I get 403 forbidden because I'm in that other account. So I'm going to go ahead and log out now. And I'm going to go back in my old account. All right, so let's try it out. I'm back here. And let's see. There we go. You can see how here it finally loads. And you can see how nice our client-side suspense loading component is. There we go. My name is, please know, the same person I'm logged in with. And you saw what happened if I try to enter this board using another user. So you can see how now we have this nice loading indicator because we are authenticating our user. Perfect. And you can see how it happens right here. It looks very smooth. So I'm going to try it just one more time by logging out and going in another account. All right. I'm in a new account here. And if I paste that previous board, it never loads for me. Perfect. And I have a bunch of 403 forbiddens. And you can see that I can never console log that user self hook information. Perfect. So this is now officially working. We successfully added authorization to our live blocks. And one thing that I want to do now is confirm that if I invite this user, which currently gets 403 to my organization, they should then be able to visit this they should no longer get a 403 so how about we do that I'm gonna go and invite myself all right so I just went back and there we go I can now join this organization and we have the board here and if I click here let's take a look there we go I can successfully see my information my name is Antonio from another account perfect so we just confirmed that all of those things are working. Also, if you're wondering where you can invite people, you can just click invite members or click manage organization here and add the email and select the role of the user you want to add. Beautiful. So this is officially working. We wrapped up the authentication for our live blocks and now we are ready to start working on this information about the board and then add the list of participants, then add our toolbar and start doing some cursor matching here. Great, great job. 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 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 used client does that fix it there we go that fixes it yeah so when you're exporting loading functions like this and if 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 infoskeleton. Something like this. Let me just try that out. So I can export infoskeleton separately. And then in here, I would import infoskeleton directly. And then I think that I can remove use client from loading. And I think everything should still work fine. yeah, this doesn't break the app. So I really wanna do that rather than this kind of export because it just feels like it's breaking my app. So I'm not gonna add use client to the loading and I'm not gonna use info.skeleton. Instead, I'm gonna export it manually. Now I'm gonna 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. All right. 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, infoprops, to accept the board ID, which is a string. And then let's add that here, infoprops, board ID. And then inside, we're going to 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 out. 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 dot DSX, 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 use query from convex react. is going to use API from convex generated API. It's going to target API board get. And we're going to 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 going to 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 going to go ahead and import image from next image. I'm going to import pop-ins from next font to Google. I'm going to import CN from lib utils. 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. All right. 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 gonna be logo. The height is gonna be 40 and the width is gonna 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 that 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 going to add board. So that's one is going to be on hover BG blue 500 with a 20% opacity. And also it's going to change the text to blue 800. And we're going to have board active, which is very simply going to 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 two, 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.class name. So it takes that Poppins font. And there we go. So just make sure that you have the font constant here, pop-ins, 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 going to be using link from NextLink. 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 going to go to a root slash. And in order not to break the button, we have to pass in as child prop when we use a 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 going to be used here. And let's return a div, which is going to render a pipe. And the class name text neutral 300 and PX 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 on 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 username model 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 going to give this button an on click here. It's going to call that on open and it's going to 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 going to 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 going to be very simple. It's edit title. Side is going to 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 into the actions ID to be data underscore ID, title, data, title. We're also going to 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 gonna 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. I 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. So now let's go ahead and let's create the participants component right here. So in order to do that, we are going to need the avatar component from ShadCN. So let's head inside of our terminal here. And let me open a new one. And we're going to go ahead and run npx ShadCN UI at latest add avatar. and using this basic avatar component, we're going to create a new component called user avatar and then we're going to render them in this list of users in the corner. So let's go ahead and do the following. We're going to go inside of app folder, dashboard, my apologies, board, board ID components, and we're going to create a new file, user avatar.tsx. And this one is going to have a hint wrapped around it, which is our reusable hint component. But it's also going to use this new components UI avatar, which we've just added. So that is an avatar, avatar fallback, and avatar image. And now let's create an interface, user avatar props, to accept a source image, which will be an optional string, a name, which is also going to be an optional string, a fallback, and a border color. So all of these things are going to be optional strings here. And now let's go ahead and export const user avatar. Let's just assign this props, user avatar props. Let's destructure them, source name, fallback and border color. And then inside of here, we're going to return a hint component with a label of name or teammate. So basically make it match whatever you did in the live kit live block out. Right in here, if we don't get the name, we write teammate. So it makes sense that you also displayed teammate here. For any reason, we just can't get the name of the user. The side of this tip is going to be on the bottom and the side offset is going to be 18. And now let's go ahead and render the actual avatar component. This avatar component will have a class name, height 8, width 8 and border 2. And for the border color, we're going to use the style prop because Tailwind uses just-in-time compilers, so you cannot do dynamic classes in that way. So it's safer to do it using just border color to be border color, or you can use the shorthand operator. And then inside, we're going to add an avatar image, pass in the source to be the source prop, and an avatar fallback. this one is going to render the fallback whatever that is well it's a string here and a class name for the fallback is going to be text extra small and font semi bold like this great so now we have our usable user avatar and we can now head back into the participants.csx right here and we can go ahead and mark this as use client and let's just refresh our app to ensure that this doesn't break the app like our info component did looks like our fix for the skeleton refactor worked so now in here we're gonna go ahead and import user avatar from dot slash user avatar and we're also going to import uh use others from liveblocks.config and use self from liveblocks.config. And then inside of the participants here, we can first get the other users by using use others hook. And then we can get const current user using use self like this. And let's add a limit to how many users we are going to show here. So const max shown users is going to be in my case, two users like that. And then let's do const has more users than the limit is going to be users.length is larger than max shown users. There we go. And we already have this ready. So let's remove this text list of users. And instead, let's add a div with a class name, flex and gap x2. And then we can use users.slice. and we are going to limit to the max shown users dot map. And in here, we can destructure the connection ID and the info of that user. So let me just see if I can collapse this map like this. Maybe it's more visible that way. And in here, we are simply going to return a user avatar. And let's go ahead and pass the key to be connection ID. Let's pass in the source to be info picture. Let's pass in the name to be info name. And let's pass in the fallback to be the first letter of the name. So info name like that. Or we can add A like this. Actually T as in teammate, right? Right now, you should not be seeing anything except if you try and copy this local host and go in another browser. So let me try doing that. I'm going to go in another browser that I have on the side here. And I'm going to log in as the user that has access to this board here. Let me just try this out. There we go. So now I'm in a new account. And you can see that. So make sure that you have another browser open. and you have to be either in incognito mode or use just a completely different browser like Mozilla or something because you can't have two logged in sessions in the same browser or use like a different session like I do with my Chromes. So there we go. Now I'm logged in. This is not who I'm logged in as. So this is not my user. This is the user that is looking at this board at the same time that I am. And now besides that, we're gonna add ourselves as well. So in order to add ourselves, we're gonna go ahead and add the following. So let's just indent this. We're going to add, if we have the current user, we're going to go ahead and render the user avatar. And we're going to pass in the source to be currentuser.info picture. And we're going to pass in the name to be open backticks. Currentuser.info name. And in parentheses, we're going to say that is you in case someone has the same name and image as you and let's go ahead and write a fallback for this also to be current user dot info name and the first character like this and there we go now you can see two users here so one is me and one is the user in my another browser also logged in. Perfect. Now that we have this, let's go ahead and add a third combination, which is if we have more users. So it has more users, we want to display another user avatar component, which is very simply going to tell the user how many other users are inside. So for that, we're going to use name backticks users dot length minus whoops minus max shown users more like that so three users more so three more two more that's how it's going to say and fallback is going to be plus users dot length minus max shown users and in order to see that now change this from two to one or maybe to zero. And there we go. Now you can see how it will say one more if you only want to display yourself and no one else. So if you change it to one, then it will allow two users to be displayed. If you change it to two, it is going to allow three users to be displayed. Perfect. So let me just try it out. I'm going to go ahead and invite another user to this board. And then we're going to see three members inside. There we go So I just joined with a third account and there we go You can see now it displays three users So if you don want to display three users you can change this to one And then you can see how it's going to look like. It will only display two users. So I think we can maybe improve this because it kind of doesn't make sense. So how about we do this? I want to display a maximum of three users. So that currently, or let's say I want to display a maximum of two users. every additional user I want them to be displayed as a number so right now I have to put one for that so it doesn't really make sense right so let's see how we can improve this all right so the reason this works like that is because I forgot so it actually it should be max shown other users because I forgot that ourselves we are not part of this users.length so that's why this is confusing because I say, okay, only show one user, but it's showing two users. It's not, it's only showing one user here, but it's only showing the one from the use others hook because we exclude ourselves from that. So yeah, you have to kind of mentally keep in mind. All right. If you say two users, that means it's only going to display these two users, which are not you, but you don't count in that. So if you want to display a maximum of three users, put the number two. If you want to display a maximum of two users, put the number one because you are the other user like that. So pick and choose the one you like. I think two is just fine and that will allow you for more than three users, they're going to look with a plus number as you just saw it. Great. So one thing that's missing is the border color. So the way we're going to do border colors is we're going to use the user's connection ID to generate a unique color for them. so let's go inside of our lib utils right here and in here just go ahead and make an array of colors as many as you want so if you want you can copy these ones from my source code just make sure they all look good with the font that's in them like this so dc2626 d9770605 5996697C3AED and DB2777. So that's my choice of color. So you can add as many as you want. The more you add, the more unique users are going to be. And now what we're going to do is we're simply going to export a function called connection ID to color. And that's going to accept a connection ID, which is a type of string, sorry, a type of number. And it's going to return back a string. And let's go ahead and do return from the array of our colors. Simply do connection ID mod colors dot length. There we go. And now let's head back inside of our participants dot TSX right here. And we're going to do a very simple thing by adding the border color and using the new connection ID to color from our lib utils like this. So connection ID to color and simply passing connection ID like this. And let's copy this and let's add that to ourselves here. and in here we're gonna pass current user connection id and for the last one we don't need to pass anything and let's try this out now so go into all the browsers that you are and refresh yourself and i think you can be you could get like a new color every time there we go so now this one is brown this one is purple and this one is yellow and this is going to also match their cursor when later we add matching cursors. Great. So you successfully added the participants here. You can try it out by adding even more people into this board if you want to. And now what we're going to do is we're going to wrap up this toolbox here. And then we're finally going to start selecting the toolbox and rendering some drawings and elements on the canvas. Great, great job. So now I want to go ahead and create this toolbar right here. And in order to do that, I first want to create individual component called tool button, which we are going to render depending on what element we want to draw or what action we want to perform from the toolbar. So we don't need to import any new packages for this one. All we have to do is go into the components and board ID and create tool button.tsx like this. And then let's go ahead and mark this as use client. Let's go ahead and import a type lucid icon from lucid react. Let's go ahead and import hint so we can explain to the user exactly what tool or action they are performing. And let's import button from components UI button. Let's create an interface tool button props to accept label, which is a string. It's going to accept an icon, which is a type of lucid icon. It's also going to accept an on click action, which will just be empty void. And we're going to have two booleans is active, which is optional, and is disabled, which is optional as well. And now let's go ahead and export const tool button. let's assign all of these props here, tool button props. So we're going to have label icon on click is active and is disabled. And let's go ahead and return a hint component where the label is going to be the prop label, side is going to be right, and side offset is going to be 14. and then inside we're going to render our button component which is going to render our icon component so we need to turn this from a prop into an icon which we can render and since this is a type of lucid icon that means that we're going to be able to immediately render it all we have to do is create an alias and make it capital i so once it's capitalized then it can be used as a component immediately. So I can just pass in icon like this. And now let's go ahead and give this button a disabled of is disabled. We're going to give it an on click on on click. Size of icon and variant is going to be dependent on the is active prop. If we are active, it's going to be board active, which we've created a while ago. So let's just revisit that to refresh our knowledge. So inside of our components UI library, we've added a new variant to the button variants with board and board active. So confirm you have both of those. If you don't, pause the video and simply add board and board active. Great. And that is it. We have our tool button now. So we can now go back inside of the toolbar itself. And now let's go ahead and remove this divs. And instead, I'm going to render the tool button from dot slash tool button like this. And let's go ahead and let's give it a label of select. Let's give it an icon of mouse pointer to from lucid react. So just make sure you add this. Let's give it an on click for now to just be an empty arrow function like that. And let me refresh my local host to see if everything is working or whether something is broken. And there we go. You can now see how I have a tool button right here. And it shows me the select label. Great. And now what we are going to do is we're going to pass in the is active to be manually false, simply so we remember that we have to change this to be modular later. And now let's go ahead and add another one for the text, whereas the icon is going to be type. You can import that from Lucid React as well. Let's copy and paste this one. And the third one is going to be a sticky note. The icon is going to be sticky note from Lucid React. And let's go ahead and do the same thing for rectangle and for ellipses. So the icon is going to be square from Lucid React. and let's go ahead and add ellipse. The icon is circle. And last one from here is going to be the pen. So a label is pen and icon is pencil from Lucid React. There we go. So make sure that you have circle, mouse pointer to pencil, So square sticky note and type icons from Lucid React. And let me just go ahead and see how this looks like. And there we go. We have a very nice selection of items here. And one is always going to be selected, right? So it's either going to be the select itself or it's going to be one of the other elements here. And now let's do the same thing for the undo and redo buttons here. so here at the bottom we're going to remove this too we're going to add tool button and this one is going to represent the label undo and it's going to have an icon undo to from lucid react we're going to have on click here to be an empty arrow function and we're going to have is disabled for now to be explicitly false and let's copy and paste this and this one is going to be redo to icon with the label redo like this there we go and this undo and redo icons are actually going to be disabled until we actually have some history inside so you can take a look at how that's going to look like they're going to be disabled until we start drawing something and then when we have history we're going to be able to undo and redo perfect so now if you refresh you can see how our loading skeleton makes much more sense because it takes almost the exact height which we are gonna have here. Perfect. So that is the basic layout for our skeleton here, sorry, for our toolbar here. So what we're gonna do next,\nis we're going to actually create an ability that when we click on one of these items, it actually stays selected and it's kept in our canvas state, which is what we are going to call, well, the state which decides which drawing tool or element is currently present. Great, great job. So now I want to implement the ability to select items from this toolbar and for them to stay selected. So in order to do that, I first want to go inside of the toolbar here and give the toolbar component some new props. So let's create an interface toolbar props here. And just above it, we're going to create a type called canvas state, which for now is just going to be any. Obviously, we're going to create this be a proper state in a moment, but let's just have it as a type here. Let's go ahead and actually pass the prop called canvas state. which is going to be a type of canvas state. Then let's give it a set canvas state, which is going to be a method which accepts new state, which again is a type of canvas state. And it is returning a void. Then let's add undo function. Let's add redo function. And now let's add can undo to be a Boolean and can redo to be a Boolean. So now with these, we can control all of this on clicks and all of this is active booleans. But now what I want to do is I want to create an actual canvas state here. So let's just give this toolbar props an assignment here. Let's extract the canvas state. Let's extract set canvas state, undo, redo, can undo, and can redo like this. And now let's go ahead, close everything, and let's create a root folder called types. And inside of it, create canvas.ts. So these are going to be common types, which we're going to use for the canvas component. And the first type that I want to create here is an Enum canvas mode. So let's export Enum canvas mode. and let's go ahead and simply set none to be the default one like this. And then let's go ahead and let's export a type canvas state, which for now is just going to be a possibility of objects, right? So I'm going to add a pipe element and then I'm going to open an object and inside I'm going to write mode to be canvas mode dot none like this. And then later in here, we're going to add some more options. So that's why I'm adding a little pipe element here. All right. So just go ahead and add this. And now let's go back inside of our app folder, board, board ID components, canvas, not canvas, toolbar. And we can remove this type canvas state any. And instead, we're going to use this canvas state here. So let's go ahead and import canvas state from at slash types slash canvas like this. And I will just separate these two imports. There we go. Now, what I want to do is I want to go inside of my canvas.vsx, ensure that this is a use client component. And now what we're going to do is a very simple set state here. So let me just prepare the imports for that. Use state from react. And let's import canvas state from types canvas. And in here, I'm going to go ahead and set the canvas state, set canvas state to be use state. And let's go ahead and pass the canvas state here. And inside, I'm going to go ahead and write mode, canvas mode from types canvas. So we have that exported as well. Make sure you export both the Ennium and the canvas state like this. So it's going to be canvasMode.none, like that. And now let's go ahead and pass that to the toolbar. So now we have the canvasState, so we can pass the canvasState. Let's also pass setConvassState to be setConvassState. And we have canRedo, I believe. Let's make that false. CanRedo, let's make that false as well. but we also have undo oops this is one of this is should be can undo we also have undo methods right and we also have redo method there we go so we need to fill this for now as well and luckily for us we can do that quite easily simply by using use history and use can undo and use can redo So let's go ahead. Let's actually keep this at the top here. Let's add const history. We use history. So that's going to be from live blocks config. That is our file, which we have in the root of our application right here, live blocks dot config. So in here, you can see we have use can redo, use can undo and use history itself. All right. So we have use history and let's also do use can undo, use can redo. So we have all of that prepared. Let's assign that. Const can undo, use can undo. Const can redo, use can redo. Like that. Let's pass in the can redo and can undo to the prompts. Make sure you don't mismatch them accidentally. So redo, redo, undo, undo. All right. And now let's go ahead and use history and pass the undo and redo methods. So that's very simply just going to be history.undo. and history.redo. There we go. And now let's go inside of our toolbar and let's actually use that for our tool button components at the bottom here. So the undo and the redo buttons. So on click, this one is gonna use undo. Is disabled is gonna be if we cannot undo. So exclamation point can undo. like this. And for this one, we're going to have redo. And it's going to be disabled if we cannot redo. So with an exclamation point as well. And there we go. Now you should have both of them disabled. So let me comment out the disable prop to see if we're going to get any error, perhaps if we try to redo, we don't get an error, but nothing happens either way. All right, so just make sure this is then disabled. If there's nothing we can redo or undo. Great. So now that we have that, we added some control for our undo and redo. How about we now enable the select button to be active? So we can do that quite easily now, simply by defining when this active prop should be true. So we can set that to be if canvas state from our prop dot mode is equal to canvas mode from our types canvas. Make sure you've added the canvas mode import here is also none like this. And there we go. You can now see that by default, my cursor here is selected because that's going to be selected when canvas state mode is canvas none. But also is going to be some more elements like translating when we are resizing, when we are pressing, and when we are making a selection net around multiple elements. And let's also add set canvas state here to add mode, canvas mode, none, like this. So if something else is selected, and once we click here, this will bring back the canvas mode to none. Great. So now what I want to do is I want to go back inside of my canvas types here, and I want to add some more types which this is going to have. So first things first, let's go inside of canvas mode and let's add all the other abilities here. So besides none, we're going to have pressing. We're also going to have selection net. We're going to have translating. We're also going to have inserting, resizing, and a pencil mode like this. So now I want to go ahead and go inside of the canvas state here and select some other possibilities of the state. So this one is going to be mode, canvas mode dot selection net like this. Then we are also going to have mode of canvas mode dot translating. we're also going to have a mode of canvas mode dot inserting and we're also going to have mode of canvas mode dot pencil we are going to have canvas mode dot pressing and we are going to have a mode, canvas mode.resizing like this. And now let's go ahead and let's use this back inside of our toolbar.tsx right here. So first, let's go ahead inside of text right here and see how we can make that be selected, for example. So I gonna go ahead and change this text to be is active if canvas state dot mode is canvas mode dot inserting like this and i'm going to add an on click here to change set canvas state and it's going to call mode to be canvas mode dot inserting like this so let's try it out now by By default, this is selected. But when I click here, there we go. You can see that now text is selected. If I click back, then this is selected. Perfect. So we are now controlling our toolbar on the canvas mode. But you might already notice that canvas mode simply isn't enough for us to determine what is selected and what isn't selected, right? Because multiple fields will be able to use the canvas mode in the insert mode. For example, sticky note will be inserting, rectangle will be inserting, ellipse will be inserting, the pencil. All of these elements will be inserting something. So we need to introduce another canvas state property, which is going to be called layer type. So we're going to create a bunch of new layer types now for each of these elements, like text, rectangle, sticky note, ellipse, and pen. So let's go back inside of our types. So inside of types, canvas.ts. And let's go ahead and let's first add some small reusable types, which we are going to need. So export type color. That's going to be a type of RGB. So R is going to be a number. G is going to be a number. And B is going to be a number. and we have to add equal sign here. Let's export type camera as well. This is going to have the coordinates X and Y. And now let's create our layer type enum. So export enum layer type. That's going to have a rectangle, an ellipse, a path, text, and a note. So I'm not going to refer to note as a sticky note anywhere outside of, you know, to the user. Internally, I'm going to call that just a note. It's easier. And now when we have the layer type, let's go ahead and let's create, let's export a type called rectangle layer. So this rectangle layer is going to have the properties Which we are going to use to determine where and how to render it on our canvas So the type is going to be layer type dot rectangle Then we're going to have x which is a number y which is a number So these are the coordinates We're going to give the height and the width which we will be able to resize we're going to have a fill which is going to be a type of color to determine what color something is and we're also going to add an optional fill value which will not be used for this rectangle layer but it will be used later in note and I found it that it's easier that all of our layers have the same types so we're just going to make the ones that are not used by all of them optional like this because later it's just going to be easier when it comes to TypeScript. Great, so now we have rectangle layer and we can now copy and paste this one and create an ellipse layer in exactly the same way. So let's rename this to ellipse layer, which is going to be a layer type of ellipse. It's going to have the X, it's going to have Y, height, width, fill and value. And that is pretty much it. And now let's go ahead and let's add the path layer. So the path is going to be for when we are drawing something. So path layer is going to have a type of path. It's going to have X, Y, height, width. It's going to have a fill, but it's also going to have points, which is going to be a matrix of numbers like this. So double twice the array. Make sure you do it like this. Great. And now let's go ahead and copy and paste this again. and let's create a text layer. So that's going to be a type of layer type text and it's going to have the X coordinate, Y coordinate. It's going to have the height and the width. It's going to have fill, but it's not going to have the points. And let's copy and paste this again. And this one is simply going to be the note layer. So layer type is going to be note. we're going to have X, Y, height, width, fill, and value. And I think that should be it. Great. And now I just want to create a couple of more, three more additional types, which we are going to reuse throughout the project so we don't forget them while we are here. So that's going to be a type point, which is very simply going to be a coordinate like this. then we're going to have a type x y width and height which is going to be used for translating something and resizing so coordinates like in above but also width and the height itself and we're also going to have an enum side so that's going to be used for when we are resizing something, we're gonna have top, we're gonna have bottom, we're gonna have left, and we're gonna have right, like this. So one, two, four, and eight, respectfully. Make sure that you add them exactly like this. And now, we can expand this canvas state to be a bit more specific when it comes to our objects here. So the first object can stay the same, but the second object, canvas mode selection net, will also need to have an origin, which is going to be a type of point. And it's also going to have an optional current property, which is also going to be a type of point. So we're going to have the origin from where we start selecting something to the current coordinates from where we currently are. And that's how we are going to calculate which the web or the net that we are using to select certain layers and elements. Great. For the translating, we're going to have the current property, which is going to be required, and that's going to be a point. And for the inserting, we're going to have to differentiate between a layer type. So these are the possible layer types when we are inserting something. Layer type dot ellipse, layer type dot rectangle, layer type dot text, and layer type dot note, like this. So just make sure all of this are in one line. All right, we have that. Canvas mode pencil can just stay as it is. No need to add anything here. For depressing, we're going to go ahead and give it an origin of point, like this and for resizing we're going to add the initial bounds to be a type of x y width and height and the corner is going to be a type of side so we are going to use the x y width and height and side for resizing so just make sure that you write those like this great great job So now we have more information, more specific information about what's actually going on inside of our canvas state. Now we can go back inside of our toolbar and we can be more specific when we are selecting something. So let's go inside of the app folder, board components, board ID components toolbar. And let's go ahead. You can already see how we have an error here because when we are inserting something, we also need to have a layer type. So you can see how much that types helps us. So first things first, let's improve when the select mouse pointer icon should be active. So it's not just when canvas mode is none. It's also gonna be when canvas state mode is the following. It has to be translating or it can be selection net or it can be pressing or it can be resizing. All of those modes will trigger this select icon to be active in the toolbar. So when we are moving something, when we are selecting something, when we are pressing and when we are resizing, all of that is going to fall back to this item being selected to indicate to the user that none of the new elements are gonna appear because they're doing a specific action like translating, selecting, pressing, resizing, or nothing at all, just moving the cursor around. And the set canvas state can stay as it is. Now let's go inside of the type text. So first things first, let's resolve the set canvas state. What we're gonna do is that if we are selecting mode inserting, we also need to add one of the appropriate layer types here. So we can see that inside of our canvas here, let's just find there we go so if mode is canvas mode inserting then layer type needs to be one of this so later if you decide to add I don't know a triangle you can do that you can create a new layer type triangle and you will have to add it here as one of the possibilities so that's what I did you know I just added all the elements I could think of and you're going to learn how I did that so that then you can add, I don't know, a triangle, a hexagon or something. So let's go ahead and add the layer type. In this case, it would be layer type.text. And we have to import layer type from types canvas. So just make sure you input layer type here. Layer type.text. There we go. No more error. And we also have to be more specific with our isActive prop here. So let's also use canvas state dot layer type is layer type dot text. There we go. So let's try and click here and there we go. It stays selected, meaning that we are properly switching the canvas state and is active. Now let go ahead and do the same thing for the sticky note So sticky note is going to call set canvas state And we can copy and paste this too So it going to be inserting but the layer type is going to be note We can also copy the is active prop and paste it here. And the is active prop is going to check for inserting, yes. But the layer type needs to be note, right? Let's try it out. So when I click on note, there we go. Note is selected. text is selected and select is selected. Perfect. So we can differentiate between those two. So I'm going to keep copying these two props on click on and is active for the rest. So for the rectangle, let's do the same thing. Canvas mode inserting and layer type is going to be rectangle. And for the is active, we're going to check if the canvas state layer type is rectangle as well. When I click here, there we go. It stays selected. Let's do the same thing for ellipse here. So layer type ellipse and layer type ellipse. When I select, there we go. And last one left is the pen. So set canvas state for the pencil right here is going to be a bit simpler. So it's not exactly going to work on inserting. The reason is that pencil is kind of going to be different because we're going to keep drawing things and we're going to have to translate that live live to the other user and only when we press our cursor up when we stop drawing is that going to become a layer so it's not like with rectangle and ellipse when we immediately click on something that's going to insert a new ellipse layer or a new rectangle or a new text or a new sticky note. But with pen, it's not gonna officially become a layer in our storage until we do the mouse button or mouse press up event. I'm not sure what's it called. Only then are we gonna create that. So that's why the rules are going to be a little bit different for the pencil here. So change the set canvas state for this one to very simply just change the mode to canvas mode pencil. We have an entire separate mode for the pencil. And the only thing to check is active that's needed is check if canvas mode is pencil. That's it. So canvas mode pencil is the simplest out of all of this here. Let's go ahead and try that out now. So I can click on ellipse and I can click on pencil. Perfect. And if I refresh, the default one should be the select icon right here. There we go. Perfect. So if you are having trouble with this, you can, of course, always visit my GitHub. I would actually recommend that you take a look at my source code while you're developing. So in here, you can go inside of types, inside of canvas.ts, and you can visit the entire source codes here and see whether you made any mistakes, whether you should change something. Great. So now what we are going to do is we're going to go ahead and make it so that when other users are present and they move their cursors, we can see their name attached to their cursor and their cursor has a color matching their presence. Great, great job. So now let's go ahead and let's build the cursor presence component, which is going to render other users cursors in real time. So in order to do this chapter, make sure that you have at least two users inside of your board, right? So you need to invite someone to your organization or if for any reason you're having trouble with that and you skip that part, you can also modify the API live blocks out right here. So if you want to, you don't have to throw 403 for a person which isn't part of your organization. So you can comment this out if for any reason you're having trouble with the invites and just want to share the URL across browsers. So you can do that little trick here. Great. So just ensure that at least two users are inside of your application. And now let's go ahead inside of the app folder, board, board ID components, canvas.tsx. And now what we are going to do is just below this toolbar, we're going to open up an SVG element. So let's write SVG like this, and inside we're going to open a G element like that. And then let's go ahead and give this SVG element a class name of height of 100 VH and a width of 100 VW like this. So inside of this g element is where we're going to store all of our drawable, resizable, translatable elements of all the way from texts to sticky notes. Everything will be inside of this svg element. And I don't know what this error is, but I'm pretty sure it's just some weird cache. Okay. And inside of this g element, I want to go ahead and add a component cursors. presence. And the reason I want to do that inside of SVG, why wouldn't I add that, you know, on the same level as toolbar, for example? Well, we could, but we are also going to reuse the cursor's presence to also render when another user is drawing in real time. So for that, it's going to have to be inside of our SVG element, because later this SVG and this G element are going to have functionality on our scroll wheel to kind of change the camera position. So that's why we need to put that inside of here. Now let's go ahead and create the cursor's presence. So inside of underscore components, I'm going to create cursors presence.tsx like that. And all I'm going to do is mark this as use client. And then I'm going to go ahead and import memo from React. Since this will often change, let's go ahead and ensure that the throttle is minimal, that the connection is fast. So for that, we're going to memoize this component. And let's go ahead and export const cursors presence to be memo and return an arrow function, which for now can just return a fragment and a paragraph of cursors. And now we have this error because we have to add display name to this cursor's presence and let's just add that cursor's presence like this. Now you can go back inside of canvas and you can import cursor's presence and there we go like that and nothing should really be visible right now I don't think that text that text probably is somewhere but we cannot see it right now. Nevertheless let's go back inside of the cursor's presence now, and I want to go ahead and I want to import useOthersConnectionIDs from LiveBlocksConfig, like that. And then I'm going to go ahead and create another component here called Cursors. And that is very simply going to get the IDs from useOther's connection IDs. And it's going to go ahead and return a fragment where we're going to go ahead and iterate our IDs.map connection ID like this. And inside, we're going to render an individual cursor element, which doesn't exist yet, but it is going to exist in a second. And let's pass in the key to be connection ID. And let's pass in connection ID to be connection ID. like that. There we go. And now let's actually use these cursors and let's render them right here inside of this fragment. So later inside of this fragment, we are also gonna have draft pencil component, which is going to track and display to all users what another user is drawing at that time. But for now, we're just gonna handle the cursor movement. And now we have to create this cursor component. So inside of underscore components, create cursor.tsx. And inside of the cursor, let's go ahead and mark it as use client. Let's import memo here from react. Let's import connection ID to color from lib utils. And let's also import mouse pointer to from lucid react so we have this connection id to color in our lib utils if you remember when we created our colors array and then we use the connection id mod put on the colors length to get a random color from the array and now we're going to use that again here first let's actually create an interface cursor cursor crops is simply going to have the connection id which is going to be a required string. So now let's write memo and let's go ahead and return this. And let's go ahead and immediately write cursor, display name to be cursor. And let's go in here and return a paragraph for now, just so we get rid of the error. And then inside we can assign the props and extract the connection id like that great so first let's get the info of the user using the connection id so for that i'm going to use use other from live blocks config let me just move that here so use other is going to use the connection id it's going to get the user and extract user info and let's go ahead and connection id is a type of number my apologies not a type of string and then we're going to have cursor to be use other connection id user user presence cursor and then we're going to go ahead and write const name to be info question mark dot name or teammate let's do that and if there is no cursor we're going to go ahead and return no and ignore this type script error for now we going to go ahead and extend the presence object in a second so we can resolve that great and then what we able we going to be able to do is extract the coordinates from our cursor The problem is right now none of these things are defined right here You can see that cursor can be a type of any and our user presence does not recognize it. So we have to go ahead inside of LiveBlocks.config and modify this. Let's go inside of LiveBlocks.config. so this root file and in here we can find just above user meta I believe the type presence so let's go ahead and we can just uncomment this line or write it if you don't have it so cursor can be an object with coordinates or null and you can see how now we no longer have that issue here and we can safely extract the coordinates here. And let's just do one more thing. And that is we're gonna go inside of our reusable component room right here. And we're gonna pass in the initial presence. So we get rid of this error right here because we now added something to the presence. So we have to go ahead and pass cursor to be null by default. And that way we no longer have the TypeScript error here. Great. So that resolves the cursor component. And now let's go inside of cursor's presence and let's just import that so we don't have the error here either. So cursor from dot slash cursor component. There we go. And now we can safely continue to develop right here. so in order to render the icon inside of an svg parent we have to use an element called foreign object so let's go ahead and add foreign object and then inside we can render mouse pointer 2 which is a self-closing tag and let's give this a class name of h5 and a width of 5 and let's go ahead and give this a style property because all of these are going to be dynamic so fill is going to use connection id to color and pass in the connection id props and we're going to have the color itself be the exact same thing with the exact same param and now we have to find a way to move the foreign object according to these coordinates right here so let's give this a style prop and pass in the transform, open backticks and write translate X, open parenthesis and open a template literal like this, PX. And then we're going to go ahead and copy this and do the same thing for the Y axis. Like this. and now let's go ahead and pass in the height to be 50 and let's give 50 to the width as well and let's give this a class name of relative and drop shadow medium like this and I believe that this already might be enough for us to showcase a cursor so go into your other browser let's try and refresh this. So we're going to debug if it doesn't work. So I am moving my other cursor, but I cannot see anything on my screen. So it looks like we didn't do something properly. So I'm going to go ahead and add some console logs, debug, and tell you what I found out. So the reason we are not seeing anything is because I completely forgot that in our canvas component, we actually need to update our presence. So what I did was I just did a console log of info and cursor in this component here. And then I made sure that in my other browser, I refreshed my screen and then I refreshed the screen here as well. So let me just expand this. And there we go. This is what I saw. I saw that I am logged in in another browser. I can see the other user, but their cursor presence is null, which is the initial presence which we've just passed in the common room component. So I remembered, yes, that is correct, actually, because I never update the cursor presence once I move. So we need to add an own mouse move event and update the cursor presence. So we're going to leave this as it is for now. And let's head back inside of our canvas.tsx component right here. And let's just collapse all of our imports from LiveBlocks here. And let's add useMutation. So useMutation is a hook which can be used to kind of broadcast or stream some changes to the entire group of people connected inside of a LiveBlocks room. So let's go ahead and create a function, const on pointer move to be use mutation. And it's going to extract a method called set my presence. And in the other argument, it's going to take the event, which is a type of react dot pointer event. And let's go ahead and open this up. Let's prevent default in here. and let's go ahead and simply do const current to be pointer. Well, we have to actually create a lib for this. So let me add for now is going to be X zero, Y zero. And let's just do set my presence and pass in the cursor to be the current like that. And then we have to add the dependency array. And I don't think we have to add anything in here for now. This can just stay as it is because we're not actually using anything yet. Later, the on pointer move, of course, is going to have a lot of options, but not for now. Let me just collapse my elements here. All right. And now what we have to do is we have to make the current actually get calculated by our pointer event and the state of the canvas and the state of the camera. So we have a couple of more things to add. So first, let's initialize the camera. That's going to be very simple. It's going to be just a simple set state. So const camera set camera is going to be use state. And we're going to pass in the default x of zero and y of zero. Basically what I just did right here. That's going to be the default camera movement. And we can already define the type of the camera thanks to our types from canvas. So in here, we define the type camera. So we can use that camera from types canvas. So just make sure that you import this. Great. And now, besides on pointer move, let's also go ahead and implement on wheel. So const on wheel is going to change the camera. so we can have infinite space once we use the scroll wheel. So that's going to be react.wheel event. And I believe I need to import use callback from React. So let me just import that. Make sure that you added use callback. Let's add an empty dependency array here. And in here, all we're going to do is set camera. We're going to get the existing camera, open an immediate object. the new x coordinate is going to be camera.x minus event.delta.x. And y is going to be camera.y minus e.delta.y. So basically what we are doing here is panning the camera based on the wheel delta. And now we can use this on wheel and on pointer move and assign it to this SVG element right here. So let's go ahead and pass in the on wheel here. And let's write on wheel. And let's add on pointer move to be on pointer move. So we have those two now. But we are still not done. There is just one more thing we have to do. We have to create a reusable util, which is called pointer event to canvas point. So we're going to use the position of the camera, the position of our pointer. and then we're going to calculate the coordinates on the screen that the user can see and where they are currently scroll that on their canvas. Because remember, thanks to this on wheel and moving the camera, they have infinite place to move their camera. So let's go ahead and go inside of lib utils. And here at the bottom, I'm going to export function pointer event to canvas point. it will accept two arguments, the event, which is react.pointer event, and camera, which is the camera event. From, for the camera type, from types camera. So I'm going to import that right here. And let's go ahead and open this function up. And very simply, it's going to return an object with an X value, which is math.round e.client, and this is not an arrow function, my apologies. So event.clientX minus camera.x, and y is going to be math.round e.clientY minus cameraY, like that. We're going to get the new coordinates for our pointer, and then we can use that right here. So let's go ahead now and let's add the current to be our new function pointer event to canvas point. So just make sure you add this import from lib utils. I'm just going to move that here. So pointer event to canvas point is going to accept two arguments, the event and the camera state like this. There we go. so this should now actually control our presence and update our cursor so let's go ahead and console log the current here make sure that you've added on pointer move to the svg element here my and let's also...\nadd the on wheel console log here. So I'm gonna add the console log and I'm gonna log X to be E dot delta X and Y to be E dot delta Y just to convince ourselves that this is actually moving. So let me go ahead and open my inspect element. And when I'm moving the cursor, you can see that I have a presence. So you can see that these values are changing every single time. That's great. And now let's try and do the following. Let's try and scroll. There you can see how when I scroll to the bottom, you can see how my canvas increases, right? I also have a vertical scroll. So my X value increases. So that's how we are going to simulate an infinite board. So we need all of that information to properly display it to other users. And I think that already, if I go into my other application now and refresh, I think I should be able to see my other user's cursor without almost any changes there we go I have my other user's cursor and I can also see my cursor well you can see that on the screen but don't worry later I'm going to join the screens but we can see that it is quite tacky right it looks like kind of laggy but we can improve that by fixing the throttling and well by removing all of these console logs right we don't need them so let's go ahead and do the following. We can now remove the console log here and we can remove the console log here as well. And now what we can do is we can go back inside of our liveblocks.config.ts and in here what we can do is we can add the throttle to be 16 like this. And this already is going to improve the behavior of our app. There we go. You can see how now it should be very, very smooth. Great. And now what I want to do is I also want to render the user's name. So you can already see that the color is matching. This is my other user and their color are matching. So now let's also add the name of that user in the sidebar in the little box below. For that, we have to head back inside of our cursor component. So that is located in app board, board ID components cursor. And now here's what I want to change. I want to dynamically calculate the width of this foreign object because we can't really use flex here and stuff like that. Well, we can, but in the inside elements, we cannot use it on the foreign object. So in here, we need to use the length of our username to calculate the width of the foreign object. So I'm going to write name dot length times 10 plus an offset of 24 for our padding. And let's go ahead now here and let's render a div element, which is going to render the name of our user. And let's give this div a class name of absolute. So make sure that your foreign object has the class name relative, so we can absolutely position it here. Left dash five, PX 1.5, PY 0.5, rounded medium, text extra small, text white, and font semi-bold. And we're going to have a style so we can add the dynamic color, specifically background color, to be connection ID to color and passing the connection ID. like that. So let's try that out now. And you can already see my cursor right here. Amazing, amazing job. So there is one more thing that I want to do. So right now, you can't really see it. But in my other browser, if I go outside of my window, what happens is that you can see this cursor right here being stuck here. What I want to happen if a user goes outside of canvas is that their pointer gets reset to null. I want their pointer to be disappeared. Because this way, if they go, you know, to the top and switch their tab, they are still positioned as being here. But that's no longer true. They're moving their cursor in another tab. So we can resolve that by going back inside of our canvas. And let's add another function called on pointer leave. And we simply have to assign that to the SVG element as well. So just below our on pointer move, let's add a constant on pointer leave. That is also going to be a mutation from where we're going to extract set my presence and all we have to do is call set my presence and pass in an object cursor null like that so let's go ahead and assign on pointer leave now to our SVG element I think this is the right handler it is on pointer leave matches on pointer leave great let me see if I can actually make this like that great so now if I refresh this and if I refresh my other element now what should happen is that there we go I'm here and if I go to the top you can see how I disappear now because I'm I'm now switching tabs in my other browser right or if I go and choose a tool my cursor disappears because that's not what I'm currently doing perfect and you can see how again it matches my color and now to wrap this up I'm just gonna show you a side-by-side example of two browsers and my cursors matching. Here we go so in here you can see that I am a user called please no and here I'm a user called Antonio so you can see that in here I can see the Antonio cursor and in here I can see the please no cursor. When I go outside of the canvas element, my cursor now disappears. Great, amazing, amazing job. As always, if you're having any trouble, you can always visit my GitHub directly. I don't know if you know this, but I actually make my GitHub commits according to the chapter. So all you would have to do is find the chapter under the number 21, and you're going to see the exact code that I have changed for this exact feature. Great, great job. All right, so now I think it's time for us to finally create the functionality that when we click on a rectangle and then click on our canvas, we actually render the rectangle. So first things first, let's go ahead back inside of our canvas. And one thing that I forgot here is to actually apply the coordinates of the camera to our SVG element right here. So right now, our on wheel is changing the movement, but nothing is actually happening. So let's go ahead and find our G element here and give it a style, transform, open backticks, translate, open parenthesis. For the first argument, we're going to use camera X coordinate and write pixels. and then we're going to write comma camera y coordinate pixels like this. Right now, if you try scrolling, nothing is going to happen. But later when we actually render something on the canvas, we're going to see whether this is working or not. Great. Now let's go to the top of our app here and let's define a constant max layers. And let's put it at a hundred. So for this tutorial, I'm going to limit the layers to a hundred. This is a good practice to have. You can increase it to a thousand, to a million if you want to, but it's a good practice to have a limit of layers inside of your canvas here. Now let's go ahead and let's revisit our types for a second. So types canvas.ts and let's go all the way to the bottom here. And I want to export a type called layer. So a layer is going to be all of our possible layers, which we've defined above. So that's going to be our rectangle layer, our ellipse layer, our path layer, our text layer, and our note layer. So just confirm that you have the rectangle layer, confirm that you have the ellipse layer, the path layer, and the last one is the text layer, and of course, the note layer. Great. Now let's go ahead inside of liveblocks.config.ts. And in here, let's go ahead and from liveblocks client, let's add some additional imports here. So we are going to need a live list, live map, and live object. And from our types, we are going to need... So from types canvas, we are going to need our newly created layer. and color. And now let's go ahead and find the storage. So let me just find where that is. There we go. Type storage just above user meta and just below the presence where we added our cursor. And I'm going to remove the comment from here. And inside, I'm going to go ahead and write layers to be a type of line map. Open pointy brackets, string, and live object. Open pointy brackets again, layer, like this. And then layer IDs are going to be live list and string inside, like that. Great. Now, let's go ahead inside of our reusable room component, so components room, and in here, let's go ahead and add initial storage to have layers to be new live map from live blocks client. So just make sure you add this import. And let's actually import everything we need from live blocks client. So live map and live object. Live map of, I did this twice. Sorry, live list is one of those. And let's also import layer from types canvas. So live map, live list and live object from live blocks client and layer from types canvas. And now in here we can go ahead and define the initial storage layers to be new live map pass in the string and then live object and pass in the layer and execute it like this And layer IDs are going to be new live list Like that. Great. Now let's head back inside of our canvas component. So that's inside of the app, board, board ID, canvas right here. and let's go ahead and let's define our layer IDs here. So const layer IDs are gonna be used storage, which we can import from at slash liveblocks.config. So let me show you that right here. So the same place where we have history, undo, redo, mutation, and other stuff. And from here, from the root, we're simply going to extract root layer IDs. So layer IDs are going to be information about everything that we have to display on our canvas. So we are going to do a map iteration over those layer IDs. And depending on the layer type, we are going to know whether to show the rectangle, sticky note, text, pencil, or an ellipse. And depending on even more information, like width, height, and the X and Y coordinates, we are going to know exactly where and how to position them on our canvas. Great. And besides the layer IDs, let's go ahead and below the camera, let's add a new state for our last used color and set last used color. That's going to be used state with a type of color from types canvas. So make sure you import color from types canvas, which is just a very simple RGB object. So R is going to be zero, G is going to zero, and B is going to be zero. This represents the black color as the initial color we are going to paint everything with. Great. Now let's create a function to insert a layer. So I'm going to do that after this. Well, actually, let's do it right here after we initialize this hooks here. I'm going to write const insert layer to be useMutation, which we already have. And in here, I'm going to go ahead and extract storage and set my presence. And from the second argument, I'm going to extract layer type, which is going to be a type of layer type, which you can import from add slash types canvas. So again, let's just collapse all of this nicely so we can refresh exactly what we need from types canvas. So make sure you add layer type as the second argument. and now we're gonna define what layer types can be added here. So it's gonna be layer type dot ellipse. It's gonna be layer type dot rectangle. It's going to be layer type dot text. And it's gonna be layer type dot note. But it's not gonna be layer type dot path. That's gonna work on its own insert function because remember, drawing is gonna be a little bit different. So that's the second argument. So make sure you write all of this in one line. And the third argument is going to be a point, sorry, position, which is a type of point. And I think we already have point imported. Let me go here. Do we have a type point? Let me just find it here. Point. We do have a type point. Do I export it? I do, but I don't import it. So make sure you import type point from here so you can work with it. and it feels like I didn't assign this properly. For some reason, this doesn't seem to be working. Point refers to a type, but it's being used as a value. Okay, how about we just continue doing this? Extract this into a function and add a dependency array. Okay, it looks like I was just missing this part. So point is now definitely referring to this import, which I've added here. So just ensure that you have the import for point. And if I don't import that, great, I have an error here. Perfect. So make sure you import point from types canvas as I did right here. Point. Great. And now what we are going to do with this information is first, let's get all the layers. So const live layers are going to be our storage. dot get layers like this. And then we're going to check if we've hit a limit on our layers. So if live layers dot size is higher or equal than max layers, which we've defined at the top in a constant, which is just a number 100, we're going to break this function. Otherwise, let's go ahead and let's load the layer IDs. So const live layer IDs are going to be storage dot get layer IDs. And then let's go ahead and define const layer ID to be an ID. And for this, I'm going to use nano ID. So let's go inside of the terminal quickly. And let's just do npm install nano ID like this. Great. And now we can import nano ID. And let me just go ahead to the top here and add import from nano ID. I'm not sure it is a default import nano ID. I think that this, yes, this looks fine. So layer ID is nano ID. And then let's actually create the new layer. So const layer is going to be new live object from live blocks client. So just make sure you add this import. I'm going to move that with the types here, not with the types, with the global imports here. So import live object from live blocks client. So now that we have a new layer ID, let's go ahead and give this a type of layer type, which is going to come from the second argument right here in this insert layer function. Then we're going to have X to be our position dot X, Y position dot Y. and in here we're going to define the default height and width of this element. So height is going to be 100, width is going to be 100 as well. You can of course change this later. You can even change it depending on the layer type. So you have all the information you want here. So if you, for example, if layer type is layer type dot text, for example, you can use the width to be 500 initially, right? You can just play around with that if you want to. But for now, just follow along exactly like I do. So you are not death prone to errors, right? And fill is going to be last used color. So that's how we're going to define the default color. And once we have our new layer, we can then push this to our live layer IDs. So live layer IDs dot push is going to get a new layer ID. and live layers dot set layer ID and layer object like this. And finally, let's call set my presence selection, an array and layer ID inside and add to history is going to be true. So let's ignore this error for now. we need to add the selection. We need to add a selection to our live block config. And let's call set state here. Yes, set canvas state mode, canvas mode dot none, like this. And let's add last used color in the dependency array here. Perfect. So now let's go ahead and resolve this little selection issue. Let's go back inside of our liveblocks.config.ts. And in the presence here, let's go ahead and load the cursor and add selection to be a string and an array. So an array of strings. And that resolves this issue with selection. But now we have to go back inside of our reusable room component. And in here, very simply, we're going to add the selection to be an empty array like this. And then we can go back inside of Canvas right here. Great. So now that we have our method to insert a new layer, we have to create a method called onMouseUp, I believe, because that's the handler. Actually, let's call it on pointer up. And then depending on the current layer mode and canvas state mode, we're going to call the insert layer if we are one of these layer types. Otherwise, we're going to call some other methods. So let me go ahead and actually do that instead of just talking about it. So make sure that you have defined the insert layer method here. and then we have on pointer move, we have on pointer leave and now let's add const on pointer up. That's also going to be useMutation and it's actually not going to extract anything from the first argument. It's just going to work with the event here and let's go ahead and add an empty array inside for now. Later it's going to be filled and then what we are going to do is write const point to be a pointer event to canvas so we're going to reuse that method and pass in our event and the camera so we know exactly where to insert something and then we're going to write if canvas state dot mode is equal to canvas mode dot inserting, we're going to call insert layer and pass in canvas state dot layer type and the point like that. Else, we're going to call set canvas state and we're going to pass in the mode to be canvas mode dot none. And let's see if there is something we can already pass inside this dependency array. So how about we pass in the camera and the canvas state? Because I think we rely on them. We rely on canvas and we rely on canvas state. Great. And let's also do one more thing. So after this if clause finishes let add history dot resume here And let also add history itself in here And also insert layer because that also uses useMutation Perfect. And now let's revisit our insert layer here, just to see if there is something we could add inside of this dependency array. So insert layer, or did we already add something? Insert layer has last used color. And yeah, that's actually the only thing we rely on here because everything else we fetch inside of this method. Great. And now what we have to do is we have to add that on pointer up method to our SVG. So let's go ahead here to our SVG and add on pointer up, on pointer up like this. So the best way to try this out is by console logging. So I'm going to go ahead and console log our points and our mode, which is going to be canvasState.mode. And let's also add layer type, canvasState layer. Do we have canvasState? Oh, we only have mode. Okay, let's leave it like this. So let's try it out. I'm gonna go ahead and open my terminal here. I will select a rectangle. And there we go. When I click, I have a mode four and I have a point right here. Perfect. So mode four is an enum, which I believe represents that something is being inserted. And I don't know if you noticed, but once I clicked on something, so once I pressed something, let me just wait for this to load. Right now, my undo and redo are disabled. But once I press, you can see that I now have undo because I finally have my history and I can undo and redo. Great. So something obviously exists inside of here, but it's not currently visible. So we did a good job with our insert layer and with our on pointer app. But now what we have to do to check whether this point is actually correct where we inserted that, whether our camera translation is working, we have to add an iteration over our layer IDs. So let's go just above the cursor presence inside of the G element and go over our layer IDs.map. Let's get the individual layer ID and we're gonna render layer preview component. Let's pass in the key to be layer ID. Let's pass in the ID to be layer ID. And on layer pointer down to be, well, for now, just an empty arrow function. And selection color. For now, let's also, let's make this empty for now as well. And now let's go inside of this underscore components and create a new file, layer preview.psx. Let's import use client. Sorry, let's mark this as use client and let's export const layer preview here. And let's return a div. So it really doesn't matter. We just want to fix the import error. And then we can import that from dot slash layer preview like this. Let's go back here. Let's go back inside of layer preview now. And let's add these props here. So interface layer preview props is going to have an ID of string. It's going to have on layer point down an event of react dot pointer event and layer ID, which is a string. And it's going to call a void. And selection color is simply going to be a string like that. And then you can extract all of those layer preview props. ID on layer pointer down and selection color. So let's explain these props a little bit. So selection color, we're going to generate that in a moment. Basically, what selection color is going to be is it's going to be an indicator that when another user has clicked on a rectangle, for example, we're going to make sure that all other users can see an indicator that someone else is moving or editing that object. And that color is going to be matching their color ID. So just for now, I'm going to make this a hex code of black, right? So just three zeros like this. And on pointer down, well, for now, we don't really have to resolve that immediately. We can also just make this completely empty and add to do fix types. Let's see if that on layer pointer down still doesn't do it. Oh, my apologies. Maybe I didn't have to move the types. So on layer pointer down. There we go. That seems to resolve the issue. Great. And now we can just leave this like this. Let's go inside of a layer preview props and let's import use memo. Sorry, let's import memo from React. So we can memoize this as well. So we're going to wrap this entire thing in a memo like this. And then we need to add layer preview display name to be layer preview. And then let's go ahead and let's find out what layer we are currently iterating over. So for that, we're going to use use storage. So const layer is use storage from our live blocks config. So we're going to call use storage. Specifically, we're going to get the root and then root layers get, not layer IDs, layers dot get, and then the ID of that layer. And if we cannot find that layer in our storage, we simply aren't going to render this layer preview. There's nothing we can do here. And here's the cool part now. The way we're going to reuse this layer preview component for all of our types, for text, for sticky notes, for rectangles, for all of those things, we are simply going to do a switch statement over the layer type. So let's write switch layer.type. And for now, let's just write a case for layer type.rectangle. And we need to import layer type from types canvas. So just make sure you add layer type here. So if layer type is rectangle, we're going to go ahead and simply return. For now, let's just say a div rectangle. And then let's add a default. we're going to add a console warn unknown layer type so in case that happens you are going to know and return now like that and that's how we are dynamically going to decide what we are rendering so now we have to actually create this rectangle object so let's go inside of underscore components and create rectangle.tsx. If you want, you can give these components some kind of prefix like rectangle layer or layer rectangle. And in here, let's create an interface rectangle props to accept an ID, which is a string, a layer, which is gonna be a rectangle layer from types canvas, on pointer down, which is going to have an event of react pointer event and id which is a string and then a void and selection color which is going to be an optional string and then let's go ahead and do export const rectangle and let's assign this props right here so rectangle props we can then extract them and I think I have a typo here. So rectangle props, ID, layer, on pointer down and selection color. And very simply, what we're going to do is we're going to extract from layer the following. X coordinate, Y coordinate, width, height and fill. And we know we're going to have those because of the rectangle layer type schema. So we know that if type layer is rectangle, we have the x, y, height, width, and fill. I also added the value because I know it's going to mess with us later, but we're not going to use value for the rectangle. And then we're going to return a rect, which is a self-closing tag. And in here, we're going to give this a class name of drop shadow MD. We're going to give it an onPointerDown to be event onPointerDown and pass in the event and ID. Style is going to be transform, open backticks, translate, pass in X in pixels, comma, Y coordinate in pixels. we are manually going to set x to be zero here and y to be zero here width is going to be width height is going to be height these are by default 100 if you remember from our insert layer method stroke width is going to be one pill is going to be let's go ahead and manually just add black for now. And let's go ahead and give this a stroke of transparent for now. Great. So let's go ahead and see if it's possible to maybe see this already or if we are missing something. So if I add a rectangle and click, it looks like nothing is appearing. So let's go ahead and debug a little bit. First thing that I'm interested in is whether this rectangle ever loads. So let's add, let's log ID and layer. Will this ever log here? Let's go ahead and click here. Looks like this is never loading So now what I want to do is check inside of my layer preview here So I have a layer So let's console log layer. And let me just add a little comma layer preview. So I know where this is coming from. Okay, so it looks like I do have an object here. I do have a layer. right here and then it looks like it doesn't have the oh well because i never rendered the rectangle i apologize for that so let's actually render the rectangle inside of here and we also have to pass in uh all of the props i completely forgot my apologies so let's render a rectangle from dot slash rectangle which we've just created let's pass in the id let's pass in the layer let's pass in on pointer down to be on pointer do we have on pointer down on layer pointer down and selection color selection color and there we go once i did that so just make sure you import the rectangle from dot slash rectangle and let me remove my console logs which I luckily don't need anymore. Let's try this out now. There we go. You can see that when I click here and I select here this is where my rectangle appears and if I click undo it disappears and let's try with my camera. As you can see I can move my camera up and down. I can completely hide this if I want to. So everything seems to be working just fine. What I'm interested in is whether another user can add their own rectangles. So this, what you're seeing is another user, my other browser. And there we go. In real time, you can see them adding their very own rectangles. Perfect. So this is now working. So what we have to do now is we have to create the functionality to move these elements, to select these elements, to transform and resize these elements and also to change their colors. So I'm going to leave it this chapter like this for now. And let's just quickly recap exactly what's going on because it can be a little bit confusing. So inside of our app folder board, board ID components, we have the canvas. Let's go step by step and see what happens. So we have an on pointer up event, which is added to the SVG element, which takes up 100% of our screen. So when we click somewhere in the screen, what happens is that on pointer up event is triggered. In here, we check if we have a canvas state of inserting. If we do, we insert the layer. So that's this action. When I select a rectangle, I have changed my canvas state because if you remember in my toolbar, when I select my rectangle, let's find it. There we go. I call set canvas state and I change the mode to inserting. Sorry, this is sticky note right here, rectangle. I change the mode to inserting and the layer type to rectangle. And then when the pointer up event fires again, it goes through this if check because canvas state mode is inserting so i pass in the layer type and the point where the user moved their mouse up so where they stopped pressing and then the insert layer has that information it has the layer type and it has the position it also has the storage and my presence because of the use mutation which we are using from live blocks So let me find insert layer. Okay. And then what we do is we confirm that we haven't reached the maximum level of our layers, which is just a constant 100. So make sure you have this defined too. And then we go ahead and generate a new layer using nano ID just to get a random ID. And then we use all the information about our current pointer, like position and X and Y coordinates. We add some default height and width. We use the last used color, which is just an empty state of RGB zero representing the color black. And then we push that to layer IDs and to live layers, which are all controlled by live blocks. And then we update the presence. We add it to history. So then automatically those hooks use can undo, use can redo and use history are automatically functional now, which you just saw, if I click undo here, this one goes away. So that works. If I click redo, it goes back. And we reset the canvas state back to the select. So after I click rectangle, it goes back to select. And now we're going to go ahead and just wrap up some other functionalities, like clicking on this, resizing this, and collecting a selection net. And then it's quite easy from there, because we're going to have a bunch of features already, and we're just going to have to modify different things inside of this layer preview component. So if switch case goes to layer ellipse, we're going to render ellipse. Sticky note, we render the sticky note. Path, we render the path. So great, great job. You are really doing great. This is a really hard project. I've had a really hard time just grasping the concept of the live board. So if you've got up to this point with or without errors, great, great job. so now let's add an ability to select one of the elements which we are able now to add to our canvas and once we are able to select an element later we will be able to transform them around to resize them and also to change their color for example or completely remove it or change the depth of the layer it's in. So first thing that I want to do is I want to go back inside of my app board, board ID components canvas. And in here we have a couple of things left unfinished. So we manually hard code the selection color and we don't pass anything from the for the on layer pointer down. So the first thing I want to resolve is the selection color issue. So I'm going to go outside of the return function here. And in here, I'm going to generate a method to get that selection color depending on which user is currently selecting that element. So we know if someone else is editing that element. So for that, we're going to get the selections from other users' presence. We can use useOthersMap from at live blocks. So that global import file that we have, We can then extract the other user and we can use other dot presence dot selection. So how do we have selection there? We have selection because in the presence type alongside our cursor, we prepared the selection. So every user will have their own selection. So that way we can keep track of who is selecting what. And now we're going to write const layer IDs to color selection. and that's going to be memoized. So let's add use memo here. So import this from react. In this use memo, we're going to go ahead and in the dependency array for now, just add these selections because we're going to work with them. And in here, add const layer IDs to color selection. Again, it's going to be a record with string inside and let's give it a default value of an empty string. And let's do for const user of selections. Let's extract from the user connection ID and the selection. And then for const layer ID of selection. We're going to go ahead and create a map using this layer IDs to color selection here. We're going to attach a layer ID to our method connection ID to color from lib utils. So make sure you've imported connection ID to color from our lib utils. Let me just collapse those two. And in this layer ID, we're simply going to pass in our connection ID for that user like that. And I think I accidentally removed something here. Yes, my apologies. So I just somehow removed this end clause here. Don't know how. Basically, make sure you have connection ID to color imported and then passing the connection ID from that user. There we go. So this is all supposed to be in one line. Let me just confirm that like this. And then what we are going to do is just return this object layer IDs to color selection like this. There we go. So make sure that you have layer this to color selection object here. We iterate over our other users selections. We structure their connection IDs and their current selection. We look at the layers they are selecting because we're going to be able to do a map or a net of multiple selections. And then we match their connection ID and create a color matching to their cursor and also in this presence bar above. So you're going to exactly know which user is currently selecting those elements. And now we can use these layer IDs to color selection down here in the selection color prop. So all we can do now is simply pass in layer IDs to color selection and passing the layer ID like this. Great. So now what I want to do is I want to go back inside of my rectangle here. So let's actually keep track of this selection color to see if we are hard coding it anywhere else. So inside of layer preview here, let's take a look. We have selection color. We have it here and we pass it to the rectangle. So that seems to be all right. But in the rectangle, we don't actually use that. And we also don't use a fill as well. So for the selection color, what we can do, we're only going to use it for the stroke. So it's not going to change the color of the object, only the outline to indicate that something is going on. So we're going to turn this into either a selection color or transparent. Like this. Naturally, this is now not going to work. So I have my eyes.\nother browser open. Let me refresh. So you're going to see another user pop up here now in a second. Let's just wait for this to load. There we go. I am selecting these, but nothing is happening. That's because we haven't implemented a method that when we click on something, we actually add to our selection presence. So we have a map to iterate over other user selections, but we are yet to implement the actual functionality for that. Great. And here's another thing that we have hard coded here before we can move on. And that's this fill, which currently is hard coded to color black, which is technically correct, because inside of our canvas, we do have, where is it? Last used color to be RGB black. But let's actually use that fill property, which is a type of color, which is basically RGB object. And let's go ahead and create a util, which is going to be called CSS, color to CSS. That method will help us transform from an RGB into a hex code. So let's go inside of lib utils here. So if you want to, you can write it along with me or you can always visit my GitHub and go directly inside of lib utils right here. And you can do that every time we are here in the utils. So this is what we have to write now, this export function color to CSS. So let's go ahead and build this. So in here, let's go ahead and export function color to CSS. So this method will accept the color property, which is a type of color from our types canvas. And you can already see that this is an RGB. So basically, we're going to use these three values to generate a hex code out of this. So let's go ahead and let's return a string beginning with a hashtag because hex codes start with a hashtag. We're going to use color.r because that's the first one. We're going to use toString16 like that. Pad start to zero like that. Then we're going to immediately add the second, which is color.g toString16.pad. start two and zero again. And then we're going to go and do the last one, which is color.b two string 16, path start two and zero. Like that. And then, well, that is it because we are immediately returning that, right? Perfect. So you can probably understand how it works. Basically, we're using the radix in the to string on hexadecimal value. So it's going to revert value like 255 or 255 to which would technically be 0xff, which converts to ff, right? You can also like do an npm package for this if you want to, right? Or you can just copy it from my utils. It really doesn't matter. You just need to find a way to transform an object of RGB to a hex code. Great. So now that we have this, we can go back inside of our rectangle component. App board, board ID components rectangle. And now we can go ahead and use that right here. So let's go ahead and check if we have the fill color, we're going to use color to CSS from live utils and pass in the fill color, or we're going to use a default gray color like this, or you can use the default black color, whatever you want it to be in case for any reason you don't pass the fill or it somehow fails. There we go. So now you can see nothing really is changing. But what should happen now is that if you go ahead inside of your board canvas and change the last used color to something different. I don't even know these values specifically, but if I choose a rectangle now and press it, it should have a different color, but it seems like it's not. So let's go ahead and debug whether we are actually passing this. Yes, it seems to still be generating plain black. What if I change everything to 255? This should be white. Oh, it looks like I just don't know RGB. There we go. It's working. So just try it with 255 and completely black. Perfect. So I'm going to bring this back to black. And now if I try, all of them are black. Perfect. So we are now no longer hard coding those colors. And now we are ready to actually create a function, which we have right here on layer pointer down. so this is the method that i was talking about so right now in my other browser here if i press this nothing happens because there is nothing happening on layer pointer down so we have to create that method so that we can actually store selection in other users and our presence so let's create on layer pointer down i'm gonna do that just below our or let's do it above layer ideas color selection here. So const on layer pointer down is going to be use mutation. And in here, let's go ahead and destructure self and set my presence. Let me just immediately go ahead and return this object just so we don't have any type errors here. So self and set my presence. And the second argument is going to be event, which is react dot pointer event. And the third argument is going to be layer ID, which is a string. And now what we're going to do is we're going to check if canvas state mode is canvas mode dot pencil, or if canvas state mode is canvas mode inserting. So if it's any of those two, we're going to break this function because it means that we are currently inserting something. So no point in trying to select a layer, or we are drawing something with the pencil. So only if we have our freehand selected here, that means that we are selecting something. If we are inserting something, which is text, sticky note, rectangle, or ellipse, then this method will not fire. And if we are using the pencil, which is the separate mode for itself. Great. So if we are doing this, let's do history, pause. Let's stop propagation. and then let's get the point where the user is selecting. So point is going to be pointer event to canvas point, which we already used, and it passes the pointer event and the camera. And now let's do if not self presence selection includes layer ID. So if we don't have this selected, In that case, we are going to make sure we select it. So set my presence, selection, and pass in the layer ID we are selecting. And let's also add to history true. Like that. And let's call set canvas state after this. So we reset the mode to canvas mode dot translating. and current is going to be our point like that. And for this, we need set canvas state. We need camera history and we also need canvas state mode like that. So now let's copy this on layer pointer down and let's append it to on layer pointer down in the layer preview here. We have no type errors. Our layer preview component accepts this normally, it seems. And let's just confirm that it passes it down to the rectangle. It does. The rectangle on pointer down is used and passes in the event and the ID. So I think that now we might already see something. I'm not 100% sure. I might just figure that we are missing something. But if I try and click here, there we go. I don't know if you can see it. There we go. You can see it on the white one. So now when another user selects, it is very visible that they are selecting. So how does this work? Well, let's go ahead and recap. So what happens is that, let's go all the way from the rectangle. When someone presses on the rectangle here, we fire the on pointer down with the event. so we have information about where the user clicked. And we pass in the layer ID so we know that this was clicked on a layer and not randomly somewhere on the canvas. Then this on pointer down goes to the layer preview and it fires the on layer pointer down, which is simply passed down from the canvas function. And then on layer pointer down ensures that this was done intentionally and not during drawing or during inserting something. And then what it does is it generates our pointer event to canvas point, which we use in combination with our camera, right? And we add that to our presence storage in the form of selection array, passing that new layer ID here. And then we also change the canvas state to translating so that we can, you're going to see it in a second. So when we select something, right now on our side, nothing is happening. but on our side it's going to be a little bit different yeah I don't know if you notice that but if you have another account when you click this is visible only for the other user but if you yourself click here it's not visible because we haven't implemented that yet but it is visible for the other user ironically so that's what we're going to do next now we're going to go ahead and implement it so that when we select on this there is like a selection bound around this and then we're going to have some handles to resize and expand that. Great, great job. Now let's go ahead and create the selection box, which is going to be visible to us, the person that is actually clicking on this element, and not just the other person like this. So let's go ahead back inside of our canvas.tsx right here. And let me just close everything but the canvas. And what we going to do is we going to go inside of our SVG element right below the layer iteration And in here we going to add a new component called selection box It going to be a self closing tag and it going to accept one prop on resize handle pointer down For now that's just going to be an empty arrow function. Now let's go inside of our components and let's create the selection box. So the selection box is going to be a client component. Let's go ahead then import memo from React like this. Also, I don't know if you noticed, but you didn't have to always define use client. So if the child element, if the child element's parent is use client, that will automatically turn this into a client component as well. I don't know if you knew that. Except if you are passing it through children. So if you did, for example, children here, then children could be a server component. But if you don't mark this as use client, this will still be a client component. The reason I like to mark it is so I explicitly know that this is a client component. Also, if you were to import this in something else, which is not a canvas, then you would get an error because it doesn't have use client in it. Just a quick tip about that. And let's go ahead and create an interface, selection box props. And let's pass in on resize handle pointer down. And let's just make it an empty void for now. And let's export const selection box. Be a memo method. My apologies, that's not how you write a memo function. Like this. selection box, display name, selection box. And let's go ahead and destructure selection box props and on resize handle pointer down. And in here, we can return a div. Well, just a div for now. So we don't have any errors. And now we can go back inside of the canvas and we can import the selection box from dot slash selection box. Great. So now we're going to continue working in this selection box right here. And the first thing I want to do is I want to properly add the types for this on resize candle pointer down. So the props is going to accept is the corner, which will have a type of side from our types canvas and initial, initial bounds, which will be a type of x, y, w, and h like that. Great. And now let's go ahead and add a constant handle width to be eight. You're going to see why we need this in a second. So it's going to be the width of our handles, which are going to appear in the corners so that we can resize our elements. What we have to do now is we have to find our layer ID. So const sole layer ID is going to be use self from live blocks config. Let's get me. And then so just import use self from live blocks config here. Then we have access to ourselves. So we're going to write me.presence.selection.length is equal to one. And let's go ahead, in that case, choose me.presence.selection, the first in that array. Otherwise, no. And now let's go ahead and write const is showing handles. So we're not always going to show the handles to resize. So we're going to use that dynamically. Use storage. Again, you can import this from LiveBlocks config. So we're going to get the root like this. And let's go ahead and write if sole layer ID. If we have sole layer ID and if root layers get sole layer ID type. Make sure you put a little question mark here because it's possible that it doesn't exist. So we are only going to show the handles if type is not layer type dot path. And we have to import layer type from types canvas. So make sure you have layer type side and this from types canvas like that. And now we have to create a hook called use selection bounds so that we actually know how to display how far to select an element. So right now it's easy. We know that we just have to use the height and some points around these boxes. But how about later when we allow a selection net? How will we know how big that selection box needs to be? Well, for that, we're going to need a reusable hook called useSelectionBounds. So as always, you can go directly in my GitHub if you want to inside of the hooks folder, or you can follow along. So useSelectionBounds. let's go ahead and import shallow from live blocks client actually let's import it from live blocks react like this and now let's import layer and let's import x y wh let's import use storage and use self from live blocks config. Now let's create a method bounding box to accept layers, which will be an array of our layer. And it's going to return X, Y, width, and height, or nothing, or if we cannot find a bound. And now let's go ahead and get the first layer. So layers zero. If there is no first layer, we can immediately break and return null. Otherwise, we are able to calculate some boundings. So let's write four. Let i is equal one. i is less than layers dot length. And i plus plus. So we incremented every iteration. now let's extract from the layer so we get the current layer using layers and then i let's extract the x y coordinates width and height and now using those we're going to be able to assign the proper values for left right top and bottom so if left is larger than the x coordinate the new left value is going to be x right and i forgot that we have to assign these values first so let me just remove this for now so before we go into the iteration loop we're going to add the initial values for left right top and bottom so let left is going to be first x let right is going to be first x plus first width so it goes all the way to the other corner let top is going to be first y and left bottom is going to be first dot y plus first dot height so all the way to the bottom and now we're going to go ahead and check whether truly these are the largest bounds we can find or if there are some other combinations for example if left is larger than x in that case left is the new x value. Then if right is smaller than the combination of x plus width, in that case, right is x plus width now. If top is larger than this layer's y coordinate, in that case, top is going to become the new y coordinate. And if bottom is smaller than a combination of y plus height. In that case, the new bottom coordinate is going to be y coordinate plus height, like that. And now what we have to do is return x to be left, y to be top, width to be right minus left and height to be bottom minus top like that. So that's our calculation method here. And now what we can do is export const use selection bounds. And let's write const selection to be use self. Let me just write const selection use self. Let's get ourselves and write me presence selection. And then let's go ahead and return use storage. Let's get the root. Let's get the selected layers to be selection.map layer ID. And we're going to get root layers get layer ID. And let's go ahead and put an exclamation point at the end to indicate that this can never be false because we have to use the filter on this. And we're simply going to filter by a Boolean. So I think if you remove the exclamation point, you might get an error, but okay, it looks like not, but I'm just going to keep it here just in case, because I think we are going to need it. And let's return bounding box and selected layers inside. and let's add a shallow, which we imported above from LiveBlock React to our user storage. So make sure you have this shallow here. Great, so that is our useSelectionBounds ready to be used. So we can go back inside of our selectionBox component inside of underscore components in the board ID here, selectionBox. And then we can do constBounds to be useSelectionBounds like this. So import EU selection bounds from hooks, EU selection bounds. And then if there are no bounds, we are going to return. No, there is nothing for us to select here. Otherwise, we are ready to create a fragment here. And then we can add a rect element, which is going to be a self-closing tag. And let's go ahead and give it a class name of fill transparent stroke blue 500. stroke one and pointer events none and let's pass in the style to be transform open back next translate and now we going to use the bounds x value in pixels and bounds y value in pixels The x is going to be zero y is going to be zero initially as a property And let's now pass in the width again from bounds width and the height from bounds height. Like this, there we go. and now just make sure that you're actually rendering the selection box in here all right and I think that this should already be working so let me just refresh my screen here so now if I try to select something so not the other user there we go you can see how we have a nice bound right here perfect and if another user tries to do it you can see how we have a matching of their color. So we can now do both theirs and we can do ours right here. Perfect. So what I want to add now is further indication that we can resize this element by adding it little handles here. So let's go back inside of our selection box component. And in here, we already prepared this boolean is showing handles. So now we can use it to actually display the handles below this rect here. So let's write if is showing handles, only then are we going to render the following. So let's write rect again, which is going to be a self closing tag. And now we have to find the corners and we have to attach it there. So let's write class name here. Fill white stroke one stroke blue 500. X is going to be zero. Y is going to be zero. And now style is going to be dynamic. So cursor in here is going to be NWSE resize. Width is going to be dynamic. So let's go ahead then add handle width in pixels. And let's go ahead and copy that for the height as well. So it matches because it's a square. And now let's add transform to put it in a corner. So let's open back text and write translate. And we're going to use bounds dot x. And we are going to add an offset of our handle width like that. And we're going to divide the handle width by two. And let's write pixels outside. So that's the first argument in the translate, then write comma, the second argument is going to be bounds.y minus handle width divided by two, again, in pixels like this. So let me go ahead and try and there we go. You can see that now when I click on something, I have a new indicator and a little box here that I can try and resize this, you can see how it shows me that I'm going to be able to do that in this or this direction right here. Great. So now we have to do that eight times for different points around our box right here. So let's go ahead and just add one more thing here, which is going to be on pointer down. And in here, we're going to extract the event. And there's just one more thing that I want to do here, which is event.stop propagation like this. And then in here, I'm going to add to do add resize handler, because we're going to copy and paste this so that we don't forget to do that. So let's copy and paste this now. And now we have to create it for a for the other corner, right? So let's change the cursor to be NS resize, the width and the height can stay the same. But the translate function is going to be different. So what this is going to do, let's actually remove it. So it's It's easier for us to do it. So we're going to write translate. The first one is going to be bounce.x plus bounce.width divided by two minus handle width divided by two, like that. So that's going to be the first argument. And the second argument is going to be bounce.y minus handle width divided by two pixels. So don't forget these pixels here. And there we go. we can see how now this one is in the middle of my top line right here. So this one is in the corner, this one is in the middle, and now we're going to create this one right here. And we have to do that throughout the entire element. Great, so this is good. So let's go ahead and I'm going to copy the first one again because I think it's going to be more similar. So let's go ahead and paste that here. so this one is gonna have a different cursor of course this one is gonna be n e s w resize like that and let me just remove the inside of the translate so it's easier for us to do this so translate in here is gonna do bounds oops bounds dot x minus handle width divided by two plus bounds dot width in pixels, comma bounds dot y coordinate minus handle underscore width divided by two in pixels. And there we go. We now have it in this corner like that. And now we have to do the other ones. So let's copy this one. And let's just indent this properly. All right. So the next one here is going to be EW resize. All right. And let's clear up the translate so it's easier for us. So the first value in translate is going to be bounds dot X minus handle width divided by two plus bounds dot width pixels. And the second argument is going to be bounds dot Y plus bounds dot height divided by two minus handle width divided by two in pixels. And there we go. That puts us here in the middle. And always make sure that your cursors are correct, right? They need to make sense where you will be able to resize. Great. Let's copy this one. Now we're gonna create the one in this corner here. So that's going to be nwse resize, NWSE resize like that. Let's clear up our translate as always. So let's write the first one is bounds.x minus handle width divided by 2 plus bounds.width pixels. The second argument is going to be bounds.y minus handle width divided by 2 plus bounds height pixels. And I think I've messed something up because it added it here at the top. Oh, it's because I do a double divide here. There we go. Now it's working. Let me see if I can kind of display this in a nicer way for you. Is this possible? Yeah, this looks clearer, right? like this okay i'm gonna do it like this for now this looks much clearer for both of us okay and now let's copy this also if you think you're not learning much for just repeating this you can just go into my github and copy it it's like i understand this is just tedious work uh all right now let's go ahead and create the middle one here so the that one is going to be ns resize and let's go ahead and modify the first value in the translate here. So that's going to be bounds X plus bounds width divided by 2 minus handle width divided by 2 in pixels. And then the other one is going to be bounds that Y minus handle width divided by 2 plus bounce height in pixels. There we go. We added that one successfully. And it goes up and down, corner, left, right, corner, up and down. Great. And two more to go. So let me copy this. Let me just align this properly. Are the other ones aligned? They are. Okay. So this one, let me ensure this is the last one. well almost last this one is going to be n e s w resize and let's clear up the translate here so this one is going to have the first one of bounds dot x minus handle with divided by two pixels and the other one bounds y minus handle with divided by two plus bounds height pixels there we go and let's confirm this is a corner perfect and we are down to the last one so this one is going to have ew resize and let's clear up the translate here so the first one is going to be bounds x minus handle width divided by two pixels and the second one is going to be bounds y minus handle width divided by two plus bounds height divided by two pixels. There we go. We've wrapped it up. And what I want to do is I just want to go through all of my transforms here. Okay, so this one is a simple transform and this one could have been displayed better. So let me just go ahead and display this in a better way. And you can do this with your code as well. if you're genuinely trying to learn how this works, right? If you don't want to just type it, if you don't care, if you just want to type it out, that's okay. But just in case you want to specifically learn how it works, you can do this. It just makes it easier for the eyes to look at it in this way. And I have this one left, right? You can, of course, just visit my GitHub. I'm also doing this so it's easier for you to look at in the final GitHub. I know this is tedious work, but I don't know what is a better way to kind of teach you how to do this. You just have to do it for all of the points we need. Okay, so we have this. Let me just collapse this as well. Okay, so just make sure all of your translates are correct. You can always visit directly in my GitHub and copy the individual transform methods here if you having any problems And of course confirm that when you select something it matches So this should be up or down, this should be the corner, left, right, corner, up, down, left, corner, left, right, and another corner. Great. So what we're going to do next is the actual functionality to move these layers around when we hold. And when we hold on each of these boxes, that's going to fire this event, which we having to do, which is going to be called on resize handle pointer down. Great, great job. Again, if you're having problems with this, you can always visit my GitHub and just find the correct transforms for this. So now let's go ahead and actually enable the resizing functionality. So this is how we are going to do that. First things first, we are going to leave the on pointer down to be empty for now. So we are not going to immediately add this. Instead, let's just create this on resize handle pointer down in the canvas component. So inside of canvas.tsx right here, I'm going to go above this all pointer stuff. So let's see, we can do it here where we have insert layer. So above on wheel. Let's write const on resize handle pointer down. So when the user clicks on one of these little boxes here, doesn't start to drag yet, just clicks and this, that's when this function is going to be fired. So that is going to be a callback function. So use callback like this. And in here, we're going to go ahead and have the following props. So we're going to have corner, which is a type of side from our canvas types. So make sure you import this from types canvas. Besides corner, it's also going to have the initial bounds. which are gonna be our X, Y, width, and height from types canvas as well. So make sure you have side and this from canvas. And now let's pause history. And then let's do set canvas state. And let's change the mode to be canvas mode dot resizing. And if we pass resizing, we also need to pass the initial bounds. and we also need to pass the corner that is being resized. And now we can pass the history in the dependency array here. Now let's use this onResizeHandlePointerDown and let's actually pass it to our selection box here. So onResizeHandlePointerDown, we'll be calling the onResizeHandlePointerDown function right here. And now let's go inside of the selection box And let's actually use that function now. So inside of the on pointer down here in the first handle, let me just find it. We have a to do add resize handler. Let's go ahead and write on resize handler pointer down. We're going to call side dot top plus side dot left and bounds in the second argument like that. So let's just see where we have the side from. We have the side from types canvas. So basically, this is a corner bound, right? So you can see that when we resize, we are resizing to the top and to the left, right? And we also pass in the bounds as the second argument. So now we're going to copy this, go to the other handler right here. The other handler is simply going to be side top. like that. So that's this one. It just goes to the top. And then we go to the third one. This is again a corner one. So that one is going to be side top plus side dot right, right? Because it's a this corner right here. So now let's go ahead and go to the right one right here. So this one is very simply gonna go to side right, like this. That's this one. And then again, we have a bottom corner here. So we're gonna use two of those. Let's go ahead and let's combine the side bottom and side right. And pass in the bounce as usual. And now let's go ahead to the lower one. And this one is just side bottom, right? It's this one right here. So we don't have to pass in side right like that. Great. And let's go ahead and do the next one, which is this corner, which is bottom and left. So lower one. this is going to be side bottom plus side dot left like that and now let's go ahead and do the left one oh is this the last one oh yes because yes yes yes correct correct and this will just be side dot left because all it does is goes inside of the left so let's go from bottom to top to confirm. So this one, the last rect is our side dot left. So that represents this one. Okay. The one above it is this corner right here, which should be bottom and side left. Then the one above that one is simply side bottom because it just goes down right here. the one above that is bottom and right because it is this corner right here the one above that one is side right because it very simply just goes to the right so the opposite of our last one which is left and then above that one we have a corner one so side top plus sad right, which is this one. And then after that one, we simply have side top. So it simply goes to the top. And the first one is simply a side top plus side left, which is this right here. Perfect. So now we are ready to go back inside of our canvas. And right now, if you try, still nothing here is happening, but inside of your own resize handle pointer down, you should be getting some console logs here. So let's go ahead and console log the corner and the initial bounds so we can see this happening right here. So I'm going to go ahead and open my terminal here. Let me try and selecting this one. If I click here, there we go. Corner nine, corner eight, corner 10, corner 2, corner 6, corner 4, corner 5, like that. So all of them are different, perfect, and we also have the initial bounds of their width, height, and their coordinates, so we can calculate using those. Great, and now what we have to do is the functionality to actually do something when we drag these items. So the way we are going to do that is by going inside of our, let's go ahead and find on pointer up or on pointer move. I'm not sure which one would be the correct place to put this. Right, it should be on pointer move. So let's go inside of on pointer move right here, where right now all we do is simply set the presence of the cursor. So now what we're going to do is we're going to check if we have canvasState.mode to be canvasState.resizing. We have to use a type canvasState. My apologies, canvasMode.resizing. If that is true, let's just console.log resizing. So now if we are correct, so let's find our onResizeHandlePointer down. What it does, it changes the canvas state to canvas mode resizing and it passes the initial bounds in the corner we are trying to resize. So then inside of our onPointerMove, we should have this being logged once we are moving a corner icon. So let's try it out. So now if I go ahead and do this, right now just by moving my cursor, nothing is happening. If I randomly drag the element, nothing is happening. But if I drag this, it should be happening, but it seems like nothing is still happening. So I think we are missing an additional event handler here. It might be because I forgot to add a dependency array. Let's see if that is true. It could be. So we need kind of a state inside of the dependency array. Let's see if that will maybe help it. There we go. So I was missing the dependency array. There we go. You can see how if I hold and drag anywhere, nothing is happening. But if I specifically grab a handle, then we have a bunch of resizing events happening. So that's exactly what we want. Perfect. So just make sure that inside of your own pointer move, you've added an if clause to check if we are in the resizing mode and then we are doing the resizing. Perfect. So what we have to do now is we have to create a method resize selected layer so that we can actually, well, resize it. So I want to add this in here where we have our insert layer. So just below that, let's go ahead and let's add the function to resize the layer. So const resize selected layer is going to be useMutation again. And in here, let's go ahead and let's extract the storage and self. And let me just write this function so we don't have any prop errors here. So storage and self and point, which is a type of point, which we have from types canvas. And now in here, we're going to go ahead and check if canvasState.mode is not canvas mode resizing. So if we somehow fired this function\nAnd we are no longer resizing or just break the function. And now what we have to do is we have to create a util to resize the bounds. Let's head inside of our utils file. Then again, you can always visit this in my GitHub. We have the entire utils file here. If you don't feel like writing this line by line with me, if you do, then let's go and write export. function resize bounds, which is going to accept bounds as the first argument, which is a type of x, y, width, and height, corner, which is a type of side, point, which is a type of point, and let me just collapse all of my props here, and it looks like I didn't import side from types canvas, and point from types canvas. So make sure you have the camera, the color, the point, side, and x, y, width, and height from types canvas. And it's going to return x, y, width, and height. So let's prepare the result to be x bounds.x, y bounds.y, width, bounds.width. height bounds that height. And let me just fix my typos here so I don't break the entire app. All right. And what we're going to do is return that result. So this will resolve this type script error. And in between returning the result and the initial result, we have to do some calculations to change the actual output, which is going to happen depending on what the user is dragging, how long they are dragging and what direction they're dragging it in. So if, let's open double parenthesis, if we have a corner and side.left is equal to side.left, in that case, result.x is going to be math.min, so the smaller value of this, point.x, or bounds.x plus bounds.width. And result.width is going to be math.absolute bounds.x plus bounds.width minus point.x. Now let's go ahead and check if corner and side right is equal to side right. In that case, result.x is going to be math.min between point x and bounds.x and result.width is going to be math absolute point x minus bounds.x. Now let's go ahead and write if corner and side top is equal to side top. In that case, result dot y is going to be math min between point y, bounds dot y, plus bounds dot height. And result dot height is going to be math absolute, bounds dot y, plus bounds dot height, minus point dot y. and last thing we have to do is the bottom so if corner and side bottom is equal to side bottom like this result dot y is math min between point y and bounds y and result.height is going to be map absolute point y and minus bounds.y coordinate like this. So let's just confirm we have the necessary cases. So we have the side bottom case. We have the side top case handling here. We have the side right handling here and we have the side left handling here. Okay. and now what we can do is go back inside of our app folder board board id components canvas and now what we are going to do is we're going to actually get this function and get the bounds so let's write const bounds to be resize bounds from lib utils so just make sure you import resize bounds from lib utils the same place we have the connection id color and pointer event to canvas point. And in here, we're going to pass the canvas state dot initial bounds, we're going to pass the canvas state corner. And we're going to pass the point that we have from the event. And then what we're going to do is write const live layers to be storage dot get layers. And then const layer individually is going to be live layers dot get self dot presence dot selection first in the array. If we have that layer, we're going to do layer dot update and pass in the new bounds for that layer. And very important, passing the canvas state in the dependency array. Like that. So let's try it out. And we're actually not just yet. So right now it's not going to work because we are not using this method anywhere. so let's go inside of our pointer move method where is it pointer move there we go on pointer move use mutation where we have the console log resizing now let's actually use the resize selected layer here and pass in current and let's go ahead and add some more values here so we need the canvas state, but we also now need the resize selected layer. So let's go ahead and try this out now. Let's also add the camera in here. I forgot that we need camera as well, canvas state, and this is okay for now. So let's try it out. If I click here and if I start resizing, there we go. I can resize in the corner. I can resize on the side here. I can resize up or down. I can resize left or right. Perfect. But it does seem kind of glitchy to me. So it could be that I made some mistakes in one of the functions. So we're going to go ahead. Well, I'm going to go ahead and see exactly where I did wrong. Yeah, especially when I grabbed the complete opposite side. this is not exactly how I would expect it to behave but yeah we are doing something that's good so now I'm just gonna go ahead throughout individually looking at looking through my well this method right called resize bounds and I'm also gonna go through is it rectangle no it's selection box. I'm also going to go look through this here. So I'm going to end the chapter like this for now. If you want to you can go ahead and play around yourself and try to fix it. Just make you know a commit so you don't mess up your code. And in the next chapter I'm going to go ahead and see how we can improve this. Great great job. All right so what we have to do now is fix this little bug that we have. So the bug seems to happen. It looks like any corner I grab, it translates to me grabbing the lower right corner. So no matter where I start, I end up there, right? So I compared with my source code and I forgot that end end operator and a bitwise operator are not the same thing. So what I intended to use here, but completely forgot about the importance of is that we're going to use the bitwise operator here. So change all of your double and end quotes here and replace them with a bitwise comparison. And now let's go ahead and check this out. So if I go ahead and expand, there we go. I can expand in this corner. If I go ahead top, I can expand top and bottom. Right corner, same thing. Right here, lower right. bottom, lower left, left, and corner left all working perfectly fine. So that's all that was the mistake. So inside of my resize bounds right here, which is inside of our utils folder, I use the end end operator instead of the bitwise operator. Great. So what we're going to do next is the ability to translate a layer. So right now, if I try and drag this, nothing is happening. So I can select it, I can resize it, and I can kind of move it in that way, right? But I would like to move it by pressing down and dragging it. So let's go ahead inside of our app folder, board, board ID components, canvas right here. And in here, where we have the layer preview, we pass in the on layer pointer down. And in here, we already set the canvas state to translating, which means that we have that logic finished. Remember in our previous module, we had to kind of initiate the resizing logic, which we did on this on pointer down here. So when we click here, this switched to resizing mode. So now when I click here, this switches to translating mode. So all we have to do is modify the on pointer move if we are translating. So let's find our on pointer move method. It should be somewhere up here. There we go. So we have the case for resizing. And now we have the case for translating. So we are going to prioritize translating over resizing. So let's go ahead and write if canvas state.mode is equal to canvas mode translating. And then we're going to go ahead and open this and we're going to map this with else if like that. And then inside of here, let's go ahead and add a console log translating like this, just so we confirm that this is actually happening So inside of my terminal here if I move this there we go I am translating But if I move it like this it not firing that event only if I move it strictly on this layer. Great. Now we have to create a method called translate selected layers. So let's go ahead and develop that method. I'm going to develop it right here where we have resize selected layer. So let's do const translate selected layers, because this is going to be able to handle multiple layers later when we add selection net. It's going to be use mutation as always. And let's go ahead and prepare this like that. And now I want to destructure from the first argument, the storage and self. And from the second argument, the point, which is a type of point from my types canvas import. And now in here, I'm going to check if canvas state.mode is anything other than canvas mode.translating. In that case, we can just break this function. And now let's create an offset object. So there's not going to be any complicated calculation for this one. So what we have to do is add an x coordinate to be point dot x minus canvas state dot current dot x. And then we're going to have y to be point dot y minus canvas state dot current dot y. And then let's get all of our live layers with storage get layers. And now let's go ahead and do for const ID of self.presence.selection. Const individual layer, which we are selecting in the presence, is going to be live layers.get ID. And if we can get that layer, we're going to update its position using the x coordinate layer.get x plus offset.x and y layer.get y plus offset.y. like that. And then outside of this for const, we are simply going to do set state, set canvas state, mode, canvas mode dot translating, and current is going to be the point from our event. And now let's go ahead and add the dependencies here, which are going to be the canvas state like that perfect so now we have this and now we can use this so let's use the translate selected layers down here in our pointer move where is it on pointer move so in here we're simply going to call translate selected layers and passing current as the option. And we also need to add this to our dependency array now, like this. So let's try it out. If this is working, and there we go. As you can see, I can now move my layers around. Perfect. One thing that seems to be missing, besides, well, the obvious fact that we are not controlling which layer is above another, right, is that I kind of cannot deselect this. So when I click outside, this should be deselected. But it looks like no matter what I do, unless I click on something else, this will always stay selected. So now I want to make sure that I can create the deselect option. So first, let's go back inside of the canvas and let's add on pointer down method. So I'm going to go ahead and do this right above on pointer up. So let's the const on pointer down to be use callback. So not use memo, use callback. Sorry, not use mutation, use callback. And it's going to work with the event, which is a type of react dot pointer event. And in here, we're going to get the point using pointer event to canvas point method, which we already have here, we're going to pass the event in the camera. And then we're going to check if canvasState.mode is canvasModeInserting. In that case, we can break this method because obviously we don't want to prevent them from inserting something. And then we're going to add a little comment here to do add case for drawing. And for now, what we're going to do is just use setCanvasState and do origin to be the point. and mode to be canvas mode pressing like this. And let's go ahead now and add the camera. Let's add the canvas state dot mode, set state, and start drawing. Actually, start drawing is what we're gonna have later, so we don't have that yet. And we need to set canvas state here, like that. Great, so we have on pointer down. Let's append on pointer down right here in the SVG element. On pointer down is going to be on pointer down like this. And now I want to go ahead back inside of my on pointer up method right here. And currently the only thing we do is we handle the inserting case here. So let's go ahead and modify this a bit by checking if canvas state.mode is in canvas mode.none or if, whoops, where was I? Okay, on pointer up. So we are adding another if clause. So if canvas state mode is canvas mode none or if canvas state mode is canvas mode.pressing in that case we're going to go ahead and set state set canvas state to be mode canvas mode dot none again so this will kind of unselect our layers and then we're gonna add this to be in the else clause so else if and that's important because we want this to overrule anything else and this is not enough by itself so for now what we can do is we can simply add a console log here unselect and we can try it out so if i go ahead here now and let's say i select something here and then if i click outside there we go you can see how unselect is being fired right because we have the case that now handles that. And now using this on select event, and you can see how it doesn't fire if you select on a specific element. It only fires if you click outside of anything on something that's not a layer. So now we're going to leverage that on select event to deselect all of our layers. So let's create a method called on select layers. And we can add that somewhere where we work with layers on wheel, on resize handler pointer. Okay, here it is, resize selected layer. So let's add const unselect layers here to be used mutation. Let me just prepare this method here. And in here, I'm going to extract self and set my presence here. And that is it. All I'm going to do inside is going to be if self presence selection length is more than zero. So if we selected something, in that case, we're going to reset my presence by adding selection to be an empty array. And we're also going to do add to history true like this. and then let's go ahead and let's actually use this in the on pointer up method which we just added this console log so in here we're now going to add unselect layers like this and also let's go ahead and let's add that to our dependency array here so let's try that out now so if i go here and if I click outside, there we go. My layers are now successfully getting unselected when I click outside. Perfect, so we can now move, we can resize. This is already starting to look like something, right? What we have to do next is we have to create an additional toolbar here which will open up when I select, which will allow me to change the color of the element I'm selecting and also to change the layer depth, right? So for example, if I want to hide that, if I want to hide this white element behind this black element, I'm going to do that using that toolbar. But we made so much progress already. And the great thing about all of this is that we only have to write all of this logic once. And then we just have to simply add new elements inside of our layer preview component. So don't worry, it's not like we're going to have to do some huge amount of work for these elements. The only one that's a little bit complex is the pen. But again, we finished majority of work already. And also, yeah, if you try, all of your undo's should be working. So both selecting and redoing, all of that should be working. Nevertheless, great, great job. So now let's go ahead and create the ability to change the color of our selected layer. In order to do that, we're going to have to create a component called Selection Tools, which is going to appear right above our selected component. So let's go back inside of our canvas components. So that's in board, board ID, components, canvas. And right here, outside of the SVG parent and below the toolbar, let's go ahead and create our new component, which is going to be called selection tools. And now let's go ahead and pass in the camera prop, which is going to be our camera and set last used color, which is going to be set last used color like this Now let go inside of the underscore components folder and let create our selection tools Let mark this as use client And let go ahead and create an interface selection tool, tools, props to accept the camera, which is a type of camera from types canvas. and let's use set last used color to accept a prop color, which we also import from types canvas, and it simply returns a void. And then we can export const selection tools. And very simply, let's also memoize this, so we can use memo from React. and let's add selection tools display name to be selection tools just so we get rid of the error and in here we can return a div selection tools and now let's just go ahead and destructure our props which is the camera and set last used color here and let's assign the props selection tool props, selection tools props. There we go. Now we can go back inside of our canvas component and import selection tools from dot slash selection tools the same way we did with the curses presence, for example. Now let's focus on being inside of this selection tools right here. Let's go ahead and decide whether we should render this or not. So const selection is going to come from use self, which we can import from liveblocks.config. And let's go ahead and get self using me, me.presence.selection. Like that. And now we are going to reuse our hook use selection bounds. So let's write const selection bounds to be use selection, use selection bounds, which we have from hooks use selection bounds right here where we have this big bounding box bounding box function so make sure you have that if you don't you can always visit my github and now that we have the selection bounds here let's first do if there are no selection bounds we can simply break and return null because there's nothing we can calculate here now let's define the x coordinate, which is going to be selection bounds dot width divided by two plus selection bounds dot x plus camera dot x. And y coordinate is simply going to be selection bounds dot y plus camera dot y like this. And now let's go ahead and give this div a class name of absolute padding, three, rounded, extra large, background, white, shadow, small, border, flex, and select none. And now let's go ahead and pass in the style, transform, open backticks, translate, and in here, let's go ahead and let's pass the first argument to be calculate. Go ahead and write x in pixels minus 50% like this and then the second is going to be calculate again y value minus 16 in pixels minus 100% like that. and let's see if we can already see this. So if I select something, there we go. You can see how it appears nicely above and follows my selection right here, and you can see how it works even when I move the camera around. Perfect. So using this, we are now gonna be able to create a component called Color Picker, which is then going to change the used color for this specific layer. So let's go ahead and let's do that. So I'm going to replace this with a color picker component. And I'm going to pass in the onChange prop, which for now is just going to be an empty object. So let's go inside of here and create the color picker. Let's mark this as use client. And let's go ahead and create an interface, color picker props. on change very simply it's going to accept a color which is a type of color from types canvas and return a void and let's export const color picker here this one is very simply going to accept on change from our color picker props here and let's return a div color picker for now just so we can go back inside of our selection tools and import the color picker, rom.slash color picker like this. And now when I click here, it should say color picker. So you can continue working in the color picker component now. So let's go ahead and quickly create a color picker, sorry, a color button. So let's first do interface color button props to be on click. And it will send the selected color, which we already have imported from types canvas and the current color representing that button. And now let's do const color button here. And in here, we're going to use those color button props. And we are going to extract on click and the color like that. Great. So we have the color button here prepared so we can return a div color button. So now inside of the color picker, we're going to use that color button. So let's go ahead and give this div a class name, flex, flex, wrap, gap to, item center, max width of 164 pixels, padding right of 2, margin right of 2, border right, and border neutral 200. And inside of here, I'm going to render a color button like this. And we can copy and paste it a couple of times. and there we go you can now see that we can render multiple color buttons here so now obviously we're going to style this better so it actually shows just a small picker for the next color so let me just remove this and let's work with one example for now so on click for now is just going to be an empty well we can pass in the actual on change right we have it in the props on change and the color can be whatever we define in RGB values. So for example, R can be 243, then G can be 82, and then B can be 35, for example. And now let's go ahead and use this inside of the color button. And let me just show something here so we can see it. So we have color button here. And now we're going to use those props to actually turn this into something. So first things first, I want to use the actual button element. I'm going to go ahead and give it a class name of the width 8, height 8, items center, flex, justify center, hover, opacity 75, and transition. Let's give it an on click to be an arrow function which calls the on click and passes in the current color for this button and inside of this button we're going to have a div the class name of h8 w8 rounded medium border border neutral 300 and more importantly than that it's going to have a style component background which is going to use our color to css which we've created when we needed to transfer the fill property in the rectangle. So it's from lib utils right here, color to CSS, and it simply transfers our RGB object into a hex code. So let's use that color to CSS and let's pass in the color. And this can be a self-closing tag. It doesn't need an end tag like this. And there we go. You can now see our beautiful color right here. So this is what I recommend you do next. If you want it, you can now create as many of these color buttons as you want here, or if you want the exact same ones, you can go and find inside of my repository, inside of board.id components, simply find the color picker inside of my repository here. And in here, you can just copy all of these color buttons, which I have prepared for you, right so there's no point in writing all of this out if you want to change the colors to something else so I'm going to replace all of my color buttons here with those there we go and let me zoom out so all of this should be in one line like this and there we go these are the colors that I like they are kind of flat colors great and now right now if you click on them nothing happens so what we have to do is we have to actually create this method inside of our selection tools or for the on change of the color picker. So inside of the selection tools here, let's go ahead and let's create a method called set fill. So const set fill is going to be use mutation from live blocks config right here. and let me just go ahead and return this. And we are going to extract from useMutation here, storage, and we are also going to extract pill, which is a type of color, which you can import from types canvas. And now let's go ahead and do const live layers. storage, get layers, and then set last used color to be the new fill color, which we've just selected. And then we going to do for the selection which we loaded right here above for the layer we are selecting for each ID live layers question mark, in case it doesn't exist, dot set, the fill value is going to be fill, like that. And let's go ahead and make sure that we added selection and set last used color in the dependency array of use mutation from live kit from live blocks. Let's go ahead and use set fill here now and let's simply pass it here for the on change and let's go ahead and see if this is already working. So if I select this one and change the color there we go. We can now officially change colors of our layers. Great. So how and why does this work? Well, you have to ensure that inside of your app folder, board ID components in the rectangle here, you are properly using the fill extraction from the layer. So make sure that you didn't leave this as hard-coded black, right? We only use the black color, well, initially, but also if for any reason we don't have the fill property. Great. So we've wrapped that up. And now what I want to do is I want to create a quick little additional action here to delete the layer because it's going to be much simpler to do than our actions to move layers up and down, which is going to be in a separate chapter. So let's go inside of our hooks folder and let's create a new hook called use delete layers.ts. Let's import useSelf from LiveBlocksConfig and useMutation from LiveBlocksConfig as well and exportConst useDeleteLayers. First, let's get the selection so we know which layers to delete. So we get me, me.presence.selection. And then let's simply return useMutation here let's prepare it like that and in here we're going to go ahead and extract the storage and set my presence from the first argument my presence like that so let's get live layers to be storage dot get layers and const live layer ids to be storage dot get layer IDs. And now we're going to do for const ID of selection, which we have live layers dot delete ID. And then const index live layers dot, sorry, live layer IDs dot index of ID, which we are trying to remove. we're going to check if index is not minus one meaning that it exists in the list of array live layer ids dot delete using that index and then what we're going to do outside of this for loop is set my presence selection to be an empty array and add to history true like that. So now let's go ahead back inside of our selection tools component where we just added the set fill for the color picker. And now what we can do is we can import that hook. So const delete layers, use delete layers from hooks, use delete layers. Make sure you added the import hooks, use, delete layers like that. And now what we have to do is simply add a little button here. So let me go ahead and import the following. We need to import the button component from components UI button. And we also need to import a hint from components hint. And I would also like to import trash to from Lucid React as an icon. Great. So now let's go just below the color picker and let's add a div here with a class name, flexItemCenter, paddingLeft of 2, marginLeft of 2, borderLeft and borderNeutral200. Let's add our hint component. And inside we're going to render our button component. So let's give the hint a label of delete very simply. and let's give the button an icon of trash too and let's give it a variant of board size of icon and on click delete layers so make sure you've added delete layers right here from use delete layers and there we go and as you can see we clearly have like extra borders here that's because in between here, there are going to be two more buttons for a position change. So let's try it out. If I click delete, oh, it looks like it is deleting, but our previous one, which I think is because of something missing inside of our dependencies array. Okay, so I'm going to go ahead and quickly debug this. So let's try this. I didn't change anything yet, but I think it's inside of the use delete layers, my dependency array is empty, but I rely on the selection from here. So let's go ahead and ensure that we pass in the selection inside of my use mutation right here. So this selection gets used. This is inside of use delete layers and nothing else is changed. Let's try it out now. And there we go. Now exactly what I click gets deleted. Perfect. So we have this working, we can change colors, we can delete, we can move, we can resize in every direction. Perfect. What we have to do next is layer, moving layers up and down above another. And then we're going to go ahead and wrap it up with our selection net so that we can select multiple items at once. And then we're going to go ahead and create a little pen effect and then easily add all other elements. Great, great job. So let's wrap up our selection tools with the ability to change whether this is in the forefront or in the background of an element. So let's go inside of our selection tools component. So I'm going to close everything here and go inside of the app folder board components. And in here we have the selection tools right here. And let's go ahead and just add the buttons. So we're going to go ahead after the color picker and add a div to hold the two buttons, which we're going to have. Let's give their wrapper a class name of flex, flex column. So they're one above another and gap Y 0.5. And now let's add a hint here and a button inside. the hint is going to have a label of bring to front and the icon we're going to use is bring to front from lucid react so make sure you import bring to front from lucid react and you can also import send to back from lucid react because we're going to need that as well so let's go ahead and give this button a variant of word and the size of icon. And now we can copy and paste this hint in the entire button inside of it with an alternative text, send to back. And let's give this a side of bottom. And this one is going to render send to back icon. So let's go ahead and see how this looks now. There we go. So you can see that this is an icon which indicates that this is going to be above those two other elements. And this one is an icon which indicates that it's kind of going behind those two elements. Great. So we're going to go ahead and implement those two functions now. So let's go ahead and let's create our move to back function. So I'm going to expand this as much as I can here. And I will also like prepare some two elements here so we can try it out. So let's go ahead here in the beginning above set fill and let's do const move to back to be use mutation. Let me prepare it like this. And from the props, let's extract the storage. And then let's do const live layer IDs to be storage dot get layer IDs. And then let's create the matrix of indices, the plural for index, const indices is going to be an array. And let's define that it's going to be an array of numbers like this. And then let's create a temporary array here, live layer IDs to array. And then we're going to iterate over them and move their position. So let index is going to be zero. It's going to go to the length of the array and iterate over each turn. If selection includes array and the current iteration, in that case, indices.push current iteration, like that. And then let's go ahead and create another for loop here. So for let i is equal to zero. i is less than indices.length, so to the maximum of that array. And let's increase our index at the end. Live layer, ids.move, indices, i, and i here. Like that. And make sure to add the selection in the dependency array of the used mutation. So basically, our positioning for what is above and what is behind is going to be controlled by their position in the array. So let's go ahead and find our send to back option here and let's add on click here, move to back. Let's save the file and let's see if we can already test this out, whether it's working or not. So for example, I have this green box and I want to move it behind this one. If I click send to back, there we go. You can see how it's being sent to back. Let me try with this one. There we go.\nIf I try with this, great. So now we have to create an equivalent, but to bring something to front. So let's go ahead and copy this method, because it's going to be quite similar. So let me just copy the entire move to back method here. And let's create this one and name it move to front, like this. So the first part of the function is going to stay the same, but we are going to have a different for loop here at the end. So the way we create indices is going to stay exactly like this. And then let's write here for let i to be not zero, but we're going to start from the end of the array. So indices dot length like that minus one. And the maximum iteration is going to be when the number is higher or equal than zero. and we're going to go in the opposite direction. So I minus minus. And then let's do live layer IDs dot remove, sorry, dot move. In the first argument, indices and the index and in the second one, array dot length minus one minus in parentheses indices dot length minus one minus I. Like that. And make sure you have the selection inside of your dependency array. And now let's use this move to front here. And let's go ahead and give it to this function, to this button, on click, where we have the icon bring to front. So let's go ahead and see if that works. So if I click on the one behind and click bring to front, there we go. That brings it to the front. If I go ahead and click bring to front here, brings it to the front. If I click on the white one, it's brought to the front. Perfect. So we just created all the functionalities which we need here for layer positioning, for changing different colors, and for deleting. What we're going to do next is create our selection net so that we can do this with multiple elements at once. So in order to create the selection net, we have to follow the flow of events happening to make this easier for us to understand. So the selection net will start on mouse point down. So let's confirm that you have that. We have an on pointer down event here. So in this on pointer down event, what's important is that we set the canvas state with an origin and canvas mode pressing. And then what we can do is go inside of our on pointer move method. And in here, add a case for what we want to happen if we are just pressing. So if we are not translating and we are not resizing, we are just pressing on the canvas. So let's go ahead and do another main if, which is going to replace this below with an else if. So if canvas state dot mode is canvas mode dot pressing, in that case, we're going to go ahead and change this to be else if, which is important because we want this to be the priority. In that case, we're going to go ahead and call start multi-selection. And we're going to pass in the current and the canvas state origin. So now we have to create the start multi-selection method. So let's go ahead and find where is the right place to put this. So I want to do it somewhere where we handle layers. So let's do it just above this resize selected layers. Let's write const start multi-selection to be use callback, not use mutation. So just use callback. And inside of here, let's prepare this with an empty dependency array. We are going to accept current to be a type of point, which we have from canvas types, from types canvas, and origin to be point as well. So we're going to know where it started and where it ends. And now let's go ahead and write if math.absolute current.x minus origin.x plus math. Whoops. So this is, yes, it's still inside of the if clause. So another math absolute, which is going to say if current.y minus origin.y. Let me go ahead and collapse this so it's maybe easier for all of us to understand. So if math absolute current dot x minus origin dot x plus math absolute current dot y coordinate minus origin dot y coordinate is less, is sorry, higher than 5. In that case, we're going to go ahead and write set state. set canvas state mode canvas mode dot selection net origin and current so why more than five what does that mean basically we decide to start the multi-selection only when the difference between the two origin and current points is larger than five so this is just a threshold which we've established that is enough for us to say, okay, what this user is trying to do is initiate a selection net. Because just by clicking on our canvas, that doesn't mean necessarily that they are trying to change the canvas state to the selection net. Only if they hold and drag for a bit so that the difference between these two points is so obvious that we can notice it on our threshold, that's when we're going to activate this canvas state right here. So let's see if we can maybe console log that already. So I'm going to add a console log here, attempting to selection net. It doesn't matter. Something that you can recognize, right? So let me go ahead here. And just clicking does nothing. but you can see if I hold and drag and kind of make a space, then it triggers that. If I just click, nothing happens. If I hold, nothing happens. But if I click, hold and move it, then that indicates the change, right? So number five is just like a magic. It seems like it's a magic number. Usually there is a practice when you're working companies, this is called the magic number and this is not like a good thing to just put in your code but this is a tutorial so you know I don't really have the highest standards right now because I'm trying to explain something here but you would probably store this in a constant for example selection net threshold or something like that right that would be a better practice to do but I'm going to leave it like this for now just not to create any more complex code because this is complex enough already so just confirm that this is working for you. And now what I want to do is go back inside of the on pointer move method. And now we're going to go ahead and add another else if here before the translate selected layers here. So let's go ahead and inject our else if here again, let's open it and let's just prepare it like this. So else if canvas state that mode is equal to canvas mode dot selection net, then we're going to go ahead and do update selection net. And we're going to pass in the current and we're going to pass in the canvas state origin. So we don't have the update selection net. So let's create it just above our start multi-selection. So update selection net is going to be use mutation because we want this to be broadcasted to everyone who is on the screen. So use mutation. And let's go ahead and let's extract the following. So from the first argument, the storage and set my presence. And from the second argument, we're going to have a current, which is a type of point. And the last argument is the origin, which is a type of point. So it's basically going to be a continuation of what was just started with the multi-selection. So in here, let's go ahead and get our layers using storage.getLayers.toimmutable. and then set canvas state. Let's write if mode, canvas mode is going to be selection net. And then we need to pass in the origin, which is going to be our origin. And we need to pass in the current. So you can do it like this in a shorthand if you want to. And now we have to create a util called find intersecting layers with a rectangle. So we're going to use that so that we define which layers did we just select by dragging using our coordinates. So let's go ahead inside of our lib utils. Again, if you don't want to write this methods, you can just go inside of my utils folder in GitHub and copy the finish method. So export function, find intersecting layers with rectangle. The first argument is going to be layer IDs, which is going to be a read-only string. The second argument is going to be layers, which is going to be read-only map with string and layer. layer is going to be from types canvas. So make sure you import the layer from types canvas here. And then we're going to have A, which is a point, and B, which is another point like this. Great. And let's go ahead and write const rectangle to be x math min between A.x and B.x. So the smaller one of the two points. Same thing for math for the y-coordinate. So a dot y and b dot y. And the width is going to be the absolute result of a dot x minus b dot x. So the two points from one another. And the height is going to be the same thing, but the y coordinate. So a dot y minus b dot y. Basically the difference between the two points on all axes of their coordinates And now let create an array of IDs here And let iterate over them So for const layer ID of layer IDs we're going to go ahead and write const layer to be layers.get layer ID. If we cannot find that layer, and let's use null specifically like this. Let's write continue. And then let's go ahead and extract from the layer XY height and width. Let's go ahead and write if rectangle.x plus rectangle.width is higher than X. and if rectangle.x is smaller than x plus width and if rectangle.y plus rectangle.height is higher than the y coordinate not react, sorry, rect height like that and let's do if rectangle dot y again is smaller than y plus height. IDs dot push layer ID. And finally, in the end, return the IDs. Great. So we're going to use this method to determine which we have selected. And now let's go back inside of our canvas. And let's go ahead and let's get our IDs. So const IDs, find intersecting layers with rectangle. So make sure you import this from LibUtils. And now in here, we're going to pass in the layer IDs. We're going to pass in the layers, the origin, and the current. And then we're going to do set my presence. Selection is going to be IDs. And remember to put the layer IDs inside of the dependency array right here. and now we have the update selection net. So I think that we can already try and select multiple items. Let's see if I drag, there we go. You can see how now my selection box is expanding on how I move my cursor. So I'm pressing down and holding, but obviously something is missing. So this is working very nicely. You can see that we don't have to write any additional code. That's it. This already works. We can change color. We can change them all to the same elements. We can remove all of them together. So all of this works. But obviously what's missing is an actual indicator of how far I'm actually reaching. Because, you know, it's very hard to understand what's going on right now. We know what's going on here because we just coded it. But let's go ahead and create a proper preview of our selection net. But that is it for our code. You can see that that works just fine. So now we just have to use those points and add a new component inside of our SVG element right here. So let's go ahead and let's find our selection box component. And just below it, we're going to add a condition if canvasState.mode is canvasModeSelectionNet. And if canvasState.current is not null, only then are we going to render a rectangle element with the following properties. The class name is going to be fill blue 500 slash five. Stroke is going to be blue 500 and stroke is going to be one. Then we're going to have the X coordinate to be math dot min, canvas state dot origin dot X and canvas state dot current dot X. and let me just see if I made any mistakes here. CanvasState.current is possibly undefined. So let me just confirm that shouldn't be an issue because of this CanvasState.current. We can use, I use the identical property. So if I just do this, there we go. Now it works. So don't use the identical property. Use just exclamation point equals like that. And let's just go ahead and copy this and do the same thing for the Y coordinate. But this one is going to use, obviously, the Y coordinates here. And now let's determine the width to be math.absolute. and let's use canvas state dot origin dot x minus canvas state dot current dot x. And let's go ahead and do height to be very similar. So it's going to be math absolute. Let's pass in the canvas state dot origin dot y minus canvas state dot current dot y. So let me zoom out to show you how all of these are supposed to look like in one line. and that should be it. Let's try it out. So now if I just randomly select, there we go. You can see how we have a nice selection net and you can see how it's very visible exactly what it is selecting. So you can see that we can move the selected area. We can go ahead and change their layering. We can change all of the elements colors which are selected. We can also delete all the elements. Great. So we've wrapped up all the functionality for the basic elements. What we're going to build next is the other elements, and then we're finally going to go ahead and render our pen. Great, great job. Now let's go ahead and let's add some more elements to our canvas. So right now, if you try and select a rectangle, that's going to work fine. But if you try and select text, for example, that will throw an error, unknown layer type, and also render a transparent or an invisible element. And same is true for everything which is not our rectangle. So we can fix that quite easily. We have pretty much everything prepared for us. And the first thing I want to do is the ellipse, since it is the simplest and the closest to the rectangle. So let's go inside of our components here. My apologies in the app folder, board components. And let's go ahead and let's pick the layer preview. So in here, we only have the case for layer type rectangle. So now let's add a case for layer type ellipse. And all we have to do is return. And inside of here, we're going to go ahead and render ellipse component, which we don't yet have, but we're going to create in a moment. Let's pass in the ID. Let's pass in the layer on pointer down to be on pointer down and selection color. to be selection color like this. And this is supposed to be on the layer pointer down. My apologies. And now let's go ahead and create this one. So ellipse.tsx. Let's go ahead and mark this as, actually, we don't need to mark it as use client. Instead, let's just import color to CSS from libutils. and let's also import the ellipse layer from types canvas. Now in here, let's create an interface ellipse props to accept an ID layer, which is a type of ellipse layer, and on pointer down, which is an event of react.pointer event and an ID of string. Selection color is going to be an optional string. And now let's export const ellipse. Let's assign ellipse props. And let's go ahead and destructure the ID, layer, on pointer down, and selection color. And now let's go ahead and let's return the ellipse element. And let's write class name, drop shadow MD. Let's write on pointer down to get the event and pass in on pointer down event and the ID of the layer. Style is going to use transform. And let's go ahead and open back mix and let's write translate. and let's go ahead and pass layer.x in pixels as the first argument and layer.y in pixels as the second argument. And let's pass in Cx to be layer width divided by 2, Cy to be layer height divided by 2. Now let's pass in Rx to be layer width divided by 2. And let's go ahead and add our Y to be layer.height divided by two. Now let's pass in fill property to be layer.fill. If we have it, it's going to be color to CSS layer.fill. Otherwise, let's use the default black color. And stroke is either going to use the selection color if someone else is selecting it, or it's going to be transparent. and stroke width is going to be one. There we go. Now we have to go back inside of the layer preview and import the ellipse component from dot slash ellipse the same way we did with the rectangle. And let's go ahead and click here. And there we go. You can see how we have an ellipse now and it picked up the last color which we used. Let's confirm that we can bring it to back, bring it to front. We can expand it. We can move it. We can delete it. And if I try it again, it's going to use the last selected color. Perfect. Let's go ahead and confirm that I can move it with other layers. I can do that as well. No problem at all. Great. So now that we have this, let's go ahead and let's create our text layer. So to create the text element, we're going to need to install a package called react content editable. So let me open a new terminal here and run NPM install React Content Editable like this And now let go ahead and let go inside of our components here and create a new file text.tsx. You can of course name it something more specifically if you wish with a prefix. Let's go ahead and import the font I want to use for text. So I found this font on Google Fonts that looks like handwriting. So I'm going to use that one. You can of course switch it out with any color, with any other font you want. Now let's import content editable from React content editable. And we are also going to need the content editable event from React content editable. Let's import CN from LibUtils and color to CSS from LibUtils. And let's import text layer from types canvas. and let's import useMutation from liveblocks.config. And now let's define our font here. So calum, I'm not sure. Subsets is going to be Latin and weight is simply going to be 400. Now let's create an interface, text props, to accept an ID. which is a string, a layer, which is a text layer, which we've imported above. And we're going to have on pointer down to be an event of react pointer event and an ID of the layer. And selection color is going to be an optional string. And now let's go ahead and let's export const text. let's go ahead and assign the text props here let's extract the layer on pointer down id and selection color in here we can extract from the layer all the info we are going to need like the x y width height fill and value and then let's go ahead and return a foreign object because we are rendering this inside of an SVG element. So we need to use foreign object. And inside, we're going to render a content editable. And let's go ahead and just pass in the HTML for now to just be text. Let's pass in the onChange event for now to be an empty onChange event like this. And now what I want to do is just a quick little positioning on this foreign object so we can start seeing this. So X is going to be X, Y is going to be Y, width is going to be width, height is going to be height. And we're going to have on pointer down here to fire an event on pointer down and pass in that and the ID of our layer. And style, we're going to have the outline here to look for the selection color. If we have it, we're going to write 1px solid, and then we're going to use selection color. Otherwise, it's going to be none. Actually, I think we need to use color to CSS here. Oh, actually, do we? Let's check ellipse. No, no, no. Selection color is a string. Yes, my apologies. now. So this will work just fine. Okay. Yes, let's try it out just like this. So now I'm going to go back inside of my layer preview and let me import text from dot slash text, the component I just created. And let's go ahead and create a case layer type dot text here. And let's return the text element here. And we're going to go ahead now and pass in the ID, which is ID, layer, on pointer down, which is on layer pointer down. And we're going to pass in the selection color to be selection color. And let's see if this will already start showing something. So when I click text, there we go. You can see that we have text written here and we can already start writing here, but this is obviously not saved and it's also not transmitted to other users. So yeah, always make sure that you also do a little testing on multiplayer, right? So you can see how I'm selecting in another browser here and you can see how that looks like. So I have an indicator that this user is selecting and moving this, the resizing works in real time. So always, you know, do a double check that all of those things are working as well. All right. So now what I want to do is I want to complete my text component so it looks a little bit better. So let's go ahead here inside of the content editable and let's give it a class name. Let's use a CN and let's give it default values of H full with full. Flex items center, justify center, text center. text center, drop shadow MD, outline none. And let's add dynamic font dot class name. There we go. So now you can see how this font now looks like it looks like it's handwritten, kind of. And now what I want to do is I want to define the color for this font. So let's give this a style. Let's give it a color. Let's check for fill. If we have filled, we're going to use color to CSS and passing the fill option. Otherwise, we're going to use the default black option like this. There we go. So now it picked up the color from the last time and I can now change it to black, blue, yellow, green, red. Perfect. So now let's go ahead and kind of enlarge our font. And what I want to do is I want to create a function which will increase the font size if I increase my a rectangle, right? So if you want to, you can also revisit your selection tools, for example. And the same way we created the color picker, you can create the font picker or the font size picker, right? You would create an equivalent action. Instead of set fill, you would create set font or set weight, whatever you want, right? And then you would simply look for that inside of, let me find the text component. You would extract that from the layer right here. And of course, you would also in your types canvas, allow that to be accepted inside of the text layer. So you would have to add the font and stuff like that. Just in case you're interested, how would you go around doing that? Right, let's focus on wrapping up the text component now. So I'm going to create this little function here. It's not going to be too complicated. So just const calculate font size is going to accept width, which is a number, and height, which is a number. We're going to define the max font size. So for me, that's going to be 96. If you want to, you don't even have to use the maximum font size, but I want to use that. Now let's add the scale factor. So this is going to be multiplied by the amount of our box. Now let's do const font size based on height. is going to be height times scale factor. And let's do const font size based on width. That's going to be width times scale factor. And let's return math.min, so the smaller of the two values, font size based on height, font size based on width, and max font size. So three arguments for our math.min method. like that. And now let's use the calculate font size in our style here. So font size, calculate font size, width and height of our layer. And there we go. You can see how now it's much bigger. So if I want to, I can make it even bigger by expanding until it reaches a limit, or I can make it smaller like that. And you can see how you can expand the text. Oh, we have, is going to have more sense when we actually add the feature to, well, synchronize the text. So how about we implement that instead first? So let's go ahead and let's create a method called updateValue. Const updateValue is going to be useMutation from, do we already have useMutation? We do, useMutation from LiveBlocksConfig. All right. So useMutation is going to go ahead and call this method here. We're going to go ahead and extract the storage from the first argument here. And we're going to go ahead and extract the new value, which is a string from the second argument here. And we're going to do const live layers to be storage dot get layers. And then live layers dot get using the ID of the layer which we have question mark in case we don't find it dot set the value field is going to be new value and we don't have to add anything inside of the dependency array because we are not depending on anything and let's go ahead and create a handler so const handle content change is going to be an event which looks for content editable event which we have imported above and simply call the above function update value with event.target.value. There we go. And let's now assign our content here. So on change is now going to just simply call handle content change. And this is going to be the value which we've extracted from the layer object right here above. There we go. So that should already be working. So let me just demonstrate that. So if we go here and change this, there we go. It is preserved. Let me go in my another browser here. So if I move this, that works. If I make it smaller, there we go. Let's refresh just to confirm this is all still working. I want to refresh here as well. There we go. It is fully preserved. Amazing And now we can easily reuse this and create our sticky note component So luckily for us the note component is very very similar to the text component So let me close everything here and let me copy the text component inside of the underscore components folder and let me rename it to note like this. So let's go ahead and import note layer. let's go ahead and let's assign note layer here let's rename this to note props let's make this be note props this is also going to be a component called note like this and now what we are going to do is we are simply going to modify how the foreign object looks and acts like so the outline can stay the same but what we are going to do is we're going to pass the background color to be fill and use color to CSS with the fill option, or it's going to use a default black color like that. And we're also going to add a class name here of Drop Shadow MD. And sorry, we're going to use Shadow MD and Drop Shadow XL. and inside of the content editable here let's go ahead and modify this a little bit as well so we no longer need the drop shadow md on the text itself because we are having it on the entire on the entire foreign object and now what we are going to do here is we are not going to use the fill color because now it's going to be just black on black right because the background color is the same here. So instead, what we're going to do is we're going to go ahead and create a util component called get contrasting text color. So let me just go ahead inside of my util here, lib utils. Let me go to the bottom here. And let's go ahead and export function, get contrasting text color. It's a function which accepts our color object. And this was honestly done using chat GPT. I don't know how it works, but basically it gets the luminance threshold values and it just returns the opposite color, I guess. So luminance is going to be 0.299 times color.r plus 0.587 times color.g plus 0.114 times color.b. And then what we're going to do is return if luminance value is higher than 228 the font is going to be black otherwise it's going to be white and surprisingly this works very very well so let's go inside of our note now and let's use get contrasting text color from lib utils like this there we go and we can leave this as as this and now we have to go back inside of the layer preview and let's copy and paste this case here let's make it for type note and let's render the note from dot slash note like this let's try it out now so if I go ahead and click on sticky note there we go you can see how this is a sticky note now the only thing I don't like is the text initially looks too big and I just think that generally the text is too big for the sticky note so what we can do is we can go inside of the notes itself and we can just quickly modify our font size for the note. So I'm going to leave the same max font size but I'm going to reduce the scale factor to be 0.15. So this is going to make it look more like a sticky note. There we go. Perfect. So let's try this out like that and let me test it out right here. Perfect. So our sticky notes are officially working. I will refresh here and I will refresh here. There we go. So we just wrapped up pretty much all of the elements which we need to work with. And let's just try it out this. So if I change the color to black, you can see that the font here has changed to white. If I change to red, it's white. If I change to yellow, it's black, So that contrasting thing obviously works. I'm not sure how, but ChatGPT sometimes does really, really well. Great. So let's just confirm we can do all of those things. And perfect. So now what I want to do next is, and the last thing I want to do is my drawing board. Great, great job. So in order to implement the pencil mode, let's start from the beginning. So the beginning starts with the event on pointer down. So let's go ahead and see what existing code we have around that. So inside of board components canvas, let's go ahead and find our event on our SVG element on pointer down. So in here, it looks like we already prepared a little to do here. So let's go ahead and write if canvas state.mode is identical to canvas mode.pencil. In that case, let's break the function. But before we do that, let's console.log and let's write drawing just so we see whether this is working or not. So I'm going to go inside of here. If I just randomly click, nothing happens. But if I select a pencil and I click, it says that I'm drawing. exactly what we want. So instead of console log drawing, we're going to go ahead and call a function start drawing. And we're going to pass in the point. And we're going to pass in the event dot pressure like that. So now let's go ahead and let's add this start drawing to our dependency array so we don't forget to do that. So right now, start drawing does not exist. But we're going to go ahead and create it right now. So let me just go somewhere where we are not doing all of this pointer events, like resize selected layer. So let's add const, start drawing is going to be use mutation. There we go. So that should now resolve the start drawing error from here. And here, we just have to align the props. So inside of start drawing here, we are going to go ahead and extract the following props. Set my presence point, which is a type of point from types of canvas and pressure, which is going to be a number like that. Now let's go ahead inside of here and let's do set my presence. pencil draft is going to be an array and then inside of it another array point dot x point dot y and pressure so make sure you do a matrix of arrays like this and then let's add pan color to be last used color and make sure you add last used color to the dependency array right here So now what we have to do is we have to revisit our LiveBlocks config so that we can extend it with the pencil draft and the pencil color. So let's head inside of our config. So that's going to be in the root here, liveblocks.config.ps. And inside of our presence, let's go ahead and add pencil draft to be an array. x number as the first argument, y number as the second argument, and pressure number as the third argument, and that itself is going to be an array of arrays, so a matrix, and null, and then color is going to be the type of color or null. I believe we already have color imported from types canvas. Great, and now what I want to do is I also want to go inside of my reusable room component, so components room and let's pass in the pencil draft to be null and let's pass in the pen color to be null as well and now if you revisit your canvas.psx again there we go set my presence now throws no errors now the next part of the flow after we call set drawing which is initialized in on pointer down. The next one is on pointer move. So let me just find where do I have on pointer move. There we go. On pointer move right here. So we have the state for pressing selection net, translating and resizing. So after resizing, last one, let's add else if canvas state mode, sorry, dot mode is identical to canvas mode dot pencil. In that case, we're going to go ahead and create a method called continue drawing. And we're going to pass in the current and the event like this. And let's also add the current drawing here. And let me just continue drawing here. And let me just also ensure that we added all the necessary elements here. So we should be having start multi selection inside of this dependency array. We should also be having the update selection net inside of this dependency array. Great. And now let's go ahead and let's actually create this method continue drawing. So I'm going to do that right here above my continue start drawing. So const continue drawing is going to be use mutation again. There we go. So just by initializing this, this should resolve that error here and here. And now let's go ahead and launch this structure from here self and set my presence. And now let's get the point to be type of point an event to be a react.pointer event. Now let's go inside of this method. And we're going to do extraction of pencil draft from self.presence. And then we're going to do if canvas state.mode is not...\ncanvasMode.pencil, or if event.buttons is not equal to one, or if pencilDraft is null. But I'm using this equal sign. I'm not using identical here. Keep that in mind. And let's also return this. And now let's go ahead and do setMyPresence. And let's pass in the cursor to be point pencil draft to be pencil draft dot length is equal to one. And pencil draft, the first array from the first array is equal to point dot X. and pencil draft from the first array. The second element is point dot Y. If that is true, we're going to add a ternary to use the pencil draft. Otherwise, we're going to go ahead and create a matrix by spreading the existing pencil draft and then creating a new stroke using point dot X, point dot Y, and E dot pressure. There we go. And make sure to pass in the canvasState.mode inside like this. There we go. So now we have our continue drawing, but there is one more event left. And that is inside of onPointerUp. So let's go ahead and find our onPointerUp event. And in here, what we have to do is right after this first if clause, let's intercept the inserting with the check for a pen. So else if canvas state that mode is canvas mode pencil. Open up this and then we're going to chain this with the else event like that. then we're going to call insert path without any props and make sure that you pass in insert path here and let's see if we are missing anything here so we should also pass in the set canvas state here looks like we are missing that we have the history we have the camera we have insert layer we have unselect layer so it's just set canvas state and insert path that we were missing so now let's go ahead and let's create this method to actually insert the path so we have to go somewhere related to our drawings let's go ahead and find start drawing let's go ahead and write const insert path to be used mutation so this should resolve those errors from on pointer up you should now normally be able to use those and let's go ahead and extract the following from the first parameter, which is going to be storage, self, and set my presence. And that's it. No other arguments here. Let's go ahead and call the live layers and use storage dot get layers here. Then let's go ahead and write const pencil draft to be self presence. and then let's check if pencil draft is null. Keep in mind I'm using the equal, not the identical sign. Pencil draft dot length is smaller than two. Live layers dot size overflows our max layers constant, which is 100. in any of those cases, set my presence pencil draft is going to be reset to null, and the function will be broken. So if we broke any of those rules, we will not be able to insert our drawing. Other than that, let's create the ID using nano ID, which we already have imported because we are using it in our insert layer method, as you can see right here. So we're going to do a similar thing here. And before we can do the following thing, we have to go inside of utils and we have to create a method called penpoints to path layer. So let's go ahead inside of our util. Again, you can simply copy this directly from my source code if you're not into writing this. So let's export function penpoints to path layer like this. It's going to accept points, which is a matrix of numbers. Whoops, like that. And a color, which is a type of color. And it's going to return a path layer, which we need from types canvas. So just make sure you import path layer from types canvas. It looks like this, and it has the points, which is a matrix of numbers. All right. Now inside of here, let's write if points.length is smaller than two, we're going to go ahead and throw new error. Cannot transform points with less than two points. So we cannot work with that params. Other than that, let's define the left to be number.positive infinity. The top to be number.positive infinity as well. the right to be number negative infinity and the bottom the number negative infinity as well. And now let's iterate over our points or const point of points. We're going to go ahead and extract the x and y coordinates from a point from the individual point like that. if left is bigger than the x-coordinate in that case left is going to become the x-coordinate if top is bigger than y-coordinate in that case our top value is going to become the y-coordinate if right is smaller than our x-coordinate in that case right will become the x-coordinate and if bottom is smaller than y-coordinate in that case bottom is going to become the Y coordinate. And now let's go ahead and let's return our type to be a layer type dot path. Let me just see if I'm having any, so return is missing the following properties. Do I need to import? Oh, I need to import layer type. Import layer type from types canvas here. And now we have to add X to be our left value. Y coordinates to be our top value. width to be our difference between right and left and our height is going to be the difference between our bottom and top and fill is going to be our color and points is going to be points.map we're going to go ahead and destructure x y and pressure from an array here and we're going to return x minus left y minus stop and pressure like that so let me see if i can collapse this like this so you can see it uh there we go and you can see no more type errors here so what we can do now is we can go back inside of the canvas here and we can wrap up our insert path function so basically what this util does is it uses the information we have from our drawing mechanism and it's going to transform that into something that we can render in an SVG element, right? So I wish I could tell you more about, you know, this comparison and everything. If you want, you can research it yourself, but it's basically a bunch of existing code that I found, chat GPT, examples from live blocks and, you know, just a bunch of trial and error. So that's why I know this part seems a little bit tedious because I'm not because I'm just writing what I'm just saying what I'm writing but yeah it's the best I can do for now now let's go ahead and continue doing this by adding live layers dot set and passing the id and new live object inside is going to be pen points to path layer which you can now import from lib utils because we just created that. And inside, pass in the pencil draft as the first argument and the last used color as the second argument, like that. And then let's go ahead and write const live layer IDs is going to be storage dot get layer IDs. And let's go ahead and write live layer IDs dot push and pass in the individual ID. And then let's update our presence by resetting the pencil draft back to null. And let's set our canvas state mode to the canvas mode at pencil. So we can continue drawing by pressing down again. And let's make sure to put last used color inside of our dependency array. So now what will happen is the following. So I'm gonna prepare my terminal here. At least I think that's what will happen. If I try and draw something once I release I going to get an error unknown layer type But as you can see this layer actually exists So we did draw something We just have no idea how to render that That's because we have to revisit our layer preview component. And in there, we have to create a new component called path. So let's go back inside of our layer preview component. and in here we're going to add a new case for layer type dot path like this and this one should not have any semicolons and we are very simply going to return a new element called path so let's already prepare everything we're going to need key is going to be id points is going to be layer dot points, then we're going to have on pointer down to be on layer pointer down. And selection color is going to be our selection color. Like that. And now let's go ahead and actually create our path dot TSX component right here. So I'm first going to install a package called perfect freehand. So npm install perfect freehand, which is going to be used for us to beautifully render our strokes. So let me just close this. Can I please close this? Okay. Now inside of here, let's go ahead and import get stroke from perfect freehand. And now what we have to do is get an interface path props with a number for the X coordinate, for the Y coordinate, a matrix of numbers for the points. And fill is going to be a string on pointer down is going to be an optional void. Let's pass in event to be a react dot pointer event. and stroke is going to be an optional string. And now let's export const path component like this. Let's go ahead and destructure the props, path props, which is going to come to XY points fill pointer down, on pointer down and stroke itself. and now we are simply going to return a path element for now like this so enough for us to import this inside of the layer preview component so make sure you import this from dot slash path and let's go ahead and see what error do we have here so let me go back inside of my layer preview here. So we are not just passing on layer pointer down. Instead in here, we're going to call the event and call on layer pointer. Actually, do I need to do this like that? Let's do it like this. Let's pass in the ID here. Let's keep this to be on layer pointer down like this. Let's go ahead inside of here. Let's accept the ID. to be a string. And let me open something for reference, like a text. So let's just confirm, yes, the ID is a string, and then this will be on pointer down event. Let me just change this. Oh, no, no, no, we can't use it like this. So no, no, no, let me refactor this. Let's go back inside of on pointer down, my apologies. So yeah, no need to add this ID. And inside of your path, simply remove that ID from the path props. So what we're going to do here is open an event and pass in on layer pointer down and individually pass the event and then the ID like that. And the selection color, let's see what is wrong with a selection color. Selection color doesn't exist. Yes, it doesn't exist because I passed in the wrong attributes here. So we are using stroke instead of selection color here. and we are also missing the x value to be layer.x and we are missing y value to be layer.y and we are missing the fill value to be layer.fill and then we're going to use color to CSS and pass in layer.fill or it's going to be in the default black color. So make sure you import color to CSS. okay and now that we have that we are still not done so now what we have to do is we have to create a method which is going to calculate using this get stroke freehand here and that function is going to be called get svg path from stroke and now i would highly highly recommend that you copy this from my GitHub, not because it's long, but because I have no idea how to explain this function to you. You're going to see what I'm talking about in a second. So this is the method which we have to create right now. I'm going to paste it here. Not that one, sorry. This one, get SVG path from stroke. So I wish I could explain to you what's going on here, but I have no idea. All I know is that it successfully transforms using the points that we have. So you can find this function, get SVG path from stroke, either by pausing the video and writing it out yourself if you want to, or you can go inside of my utils here. And in here, you have that method specifically, get SVG path from stroke. Great. So that will speed things up. Again, it's not a long function. It's just so confusing that honestly, you're going to see where we use it. So we use it inside of our app folder board components. Where is it? Our path right here. So in here, we're going to use it in combination with our get stroke. So it's specifically tailored for this get stroke perfect freehand. So if you want to explore it yourself, that's the direction you would go to. And now let's go ahead and do the following. Let's give our path here a class name of drop shadow md let's give it on pointer down to be on pointer down and let's pass in d value to be get svg path from stroke which you can now import from your lib utils like this so get svg path from stroke and then inside of it you're going to fire your get stroke, which you can import from perfect freehand like this. And inside of get stroke, you're going to pass in your points, which you have from the props, and then give it a size of 16, a thinning of 0.5, a smoothing of 0.5, and a streamline of 0.5 as well. Now, of course, you can tweak these values until you get exactly what you want. But this one's working great for mean. Now let's give it a style of transform, translate, and let's go ahead and open X in pixels and Y in pixels. The individual values of X and Y are going to be zero as props, and fill is going to be fill, stroke is going to be stroke, and stroke width is going to be one. And I think that we can already try this out. The only thing is we are not going to see it in real time, only when we release our mouse button. So I'm going to draw an eight here. And there we go. You can see how it appears, but only when I release my key, right? And I should be able to change the color normally. I should also be able to play around with layers. I could also use it in combination with something else. Great. So that is working. But what I want to do is I want to enable a real time seeing of what I'm drawing because it doesn't make much sense otherwise. So for that, we're going to go back inside of our cursor's presence component. So let's find our cursor's presence inside of components. And in here, I left a comment to do draft a pencil. So that's what we're going to do now. So my apologies, just before we do that, this will actually work for when other people are drawing in real time, right? So when another person is doing that, first, let's do it for ourselves inside of the canvas, which is going to be a little bit simpler than that. So what we have to do is go inside of the SVG right here and find the cursor presence. And then let's go ahead and use our pencil draft. Let me just see where we can extract our pencil draft from. So let's go all the way to the top of our application, where we start with our live layer IDs, right here where we have the layer IDs, the canvas state. And in here, let's go ahead and let's add our pencil draft to come from use self. You can import this from LiveBlocksConfig. And in here, we're going to use me.presence.pencil, not pen color, but pencil draft specifically like this. There we go. So now we're going to go all the way to the bottom here where we started this. If we have the pencil draft, we going to go if pencil draft is not null but using equal sign not identical And if pencil draft dot length is larger than zero in that case we going to start rendering our path component from dot slash path. So the same path, which we are reusing, where is it? Which we are reusing for our layer preview component. So that's the same component, which we've just created. and then we're just gonna have to pass all the necessary props so points are going to be pencil draft and our fill is going to be color to css from lib slash utils so make sure you add lib utils here and we're going to use the last used color and did i import color to css i did not so make sure you import color to CSS and passing the X to be zero and Y to be zero like this. And let me just see what did I break inside of my app. So I think I broke something. Just a second. I think I deleted some major key. Okay. I'm just going to go ahead and type it out again. I think I selected something above and accidentally removed it. So fill is going to be get, sorry, color to CSS from libutils and last used color. And X is going to be zero and Y is going to be zero. Yeah. Okay. Looks like I deleted somehow something from above previously. And I think we should already be seeing our own drawings here. So if I select a pen and start drawing, there we go. You can see how this is now real time for us. But here's another user and another user will draw something now. You can see how it only appears after they leave the mouse. So now we have to do the same thing, but for us to see their drawing in real time. And for that, we're gonna go back inside of our cursor's presence and we're gonna go ahead and create something called drafts. So let's go ahead. And the same way we created cursors, let's go ahead and create a const drafts so drafts is going to use others using use others mapped which you can import from liveblocks.config so in use others mapped we're going to go ahead and extract the other user and then we're going to immediately return an object and write pencil draft to be other dot presence dot pencil draft and pen color is going to be other dot presence dot pen color and we're going to go ahead and use shallow here which we can import from live blocks client let me just see that there we go and let me just collapse this too so they look nicer. All right, so make sure you've added the shallow here. And then we are simply going to return a fragment and others.map. We get the structure of key and other. And then inside of here, let's go ahead and return. Let's check, sorry, if other pencil draft return our path which you can import from dot slash path so let me just show you this dot slash path so the same component which we've just used in the canvas and layer preview pass in the key to be the key pass in the x value to be zero the y value to be zero points to be other dot pencil draft fill to be other dot pen color question mark color to css from lib utils so just make sure you import this from here and pass in other pen color or use the default black color otherwise return null if we don't have other pencil dot draft and now what we have to do is render those drafts here like this and let's try it out finally so I'm gonna go ahead and move my screen here a bit and now if I start drawing something there we go we can now see the other user drawing in real time perfect amazing amazing job you finished the entire project there are just a couple of more hooks that I want to show you which can help you improve your project so I want to show you a little hook that you can create basically what can happen is that if another user has a much larger screen than you and they move their cursor around it can happen that they trigger a scroll inside of your element here so I couldn't really reproduce that here but it did happen to me during development and here's a useful hook that you can create to prevent that So let's go inside of hooks and we're going to go ahead and create use disable scroll bounds. So use disable scroll bounds dot ts. And inside of this method is going to be very simple. We're going to import use effect from react. And then we're going to export const use disable scroll bounds. And we're going to call use effect. and very simply inside on the mount, we're going to call document.body.classlist.add overflow hidden and overscroll-none. And let's go ahead and return document.body.classlist. last list, remove overflow hidden and over scroll none. So that happens on unmount. Great. And now let's go and simply include this inside of our canvas.tsx component. So I'm going to go all the way to the top here. Right here where we start with all of our hooks, we can simply add another one. Use disable scroll bounce. You can do that from hooks right here. And let me just move that with these elements here like that. And here's another thing I want to show you. And that is shortcuts. For example, if you want to use Ctrl Z or Backspace to delete something, we can do that as well. So let's just import use effect from React. And let's go ahead and let's do it just above this. So we're going to call use effect here. and we're going to add delete layers and history inside of our dependency array. So make sure you do that before you add the delete layers. So how about to actually do that just before our render function to ensure we have all of those functions from above. Yeah. Let me move it. Okay, here is the return function. So this is where I'm going to add it. And I still don't have delete layers. That is weird. Oh, just a second. I forgot that we need to add delete layers because we don't use it here. We use it in the selection tools, but luckily that is a hook. So all we have to do is add delete layers and use delete layers. From hooks, use delete layers. There we go. So I'm gonna move that here as well now. Okay, so now we have delete layers and history. And what we can do now is we can go ahead and write a function on key down. Event is going to be a type of keyword event. And in here, you can do a switch on the E dot key. And for example, if case is Z, you can go ahead and do the following check if E is control key, or if E is meta key. And if E is shift key, in that case, you can call history.redo, else history.undo. And for the other cases, you can simply break. Sorry, let's add break right here. like that. And let's go ahead now and simply add document, add event listener and passing key down and on key down. And always make sure that you unmount the event listener to prevent any overflows. So document remove event listener and passing key down and on key down okay so let me just try this out now I will refresh this so now I should be able to use my shortcuts so if I add a new sticky note here and change the color and I press command z there we go I can remove that if I move this around let's do a lot of let go ahead and draw something If I do Command Z I remove that If I do Command Shift Z I bring it back Perfect And if you want to you can do the same thing for example to remove an element using backspace So for example, you can add a case to the backspace. And then you can do delete layers and break. so for example you can now select a sticky note and press delete right the issue with this is that if you're in the middle of typing something and press backspace that's going to remove the entire element so that's why i commented this out you can probably fix this with event propagation or more strictly checking what's going on while you're writing but yeah just comment that out if you want to allow people to remove text while they're typing. Great. But what I want to show you next is just that there are a couple of stuff which I've been told from the LiveBlocks team are deprecated. And that should be our usage of two array, I think. There we go. So we have two instances of two array. And they've told me that we can replace those with two immutable like this. So let's find, so the first one was, I'm not sure where this last one was. To immutable. Where did I use you now? So I think I changed this one. Yes, 13 seconds ago inside of selection tools. So this is the move to front function here, I believe. Right? And the second one seems to be here in move to back. So change those two to immutable. because apparently the to array is deprecated. So let me check if to array exists anywhere. It looks like it doesn't. So the way we can test this out is by checking out if our layering works. Oh, and it seems to not be working when I switch to immutable. Well, yeah, interestingly, it doesn't seem to work for me if I move it to immutable. but regardless that's just a tip for you if yours isn't working in the future it could be because you're using to array so it could be just the version of live kid of live blocks that i have so let me go ahead and find my selection tools again here and see how we can refactor this back so live layer ids to array and the one in move to back is going to go back to to array and let's see if that's going to work now so now or maybe i'm just using a completely wrong way of testing my apologies let me please try it out like this so this is working yes it could be that i just did a completely wrong test here so if i go back to immutable my apologies uh yeah i'm pretty sure it's gonna be visible now let's try it out so if i try going back yeah it works completely with two immutable and you should be using two immutable. I just tested it literally on a black on black line so that way it was not visible. Great so if you have any instance of converting two array you should refactor it to use two immutable like this. There we go that is it you finished the entire entire project. If you're interested in even more content on this project you can visit the link in the description and purchase additional content on my platform where we are going to turn this into a software as a service and implement a Stripe payment. Great, great job. Thank you so much for watching and see you in the next tutorial. So I already said goodbye to you, but I forgot that I want to explain to you how to deploy. So first things first, let's check if we are ready to deploy at all. So I'm going to go ahead and do the following. Inside of your project, you can now close everything. You don't need to have anything running. Go ahead and do npm run build. This must not have any errors for us to be able to deploy. So we can save some time instead of wasting time on Vercel. And as you can see, at least on my version of Next.js, I am getting an error. Expert encountered errors on the following paths, dashboard and not found. It looks like there is a pre-rendering error. So let's go ahead and see what's going on. I believe that is because of our convex provider. So let's go ahead and do the following. Inside of our app folder, inside of layout.tsx, let's wrap our entire convex client provider in a suspense from React. So let me do this like that. Make sure you import suspense from React and I'm going to move it here to the top. And we're going to have a nice fallback here to be our loading from components outloading. We already have this prepared. We use it. It's just our logo in the center. Great. And let's go ahead and try that out now. So I'm going to go ahead and do npm run build again. So let's see if that resolved our pre-rendering error. And do we have any more errors we need to resolve. So it looks like this is now working completely fine. Great. And here's one more thing I want to do before we push this to GitHub. And that is that inside of your app folder, API, live blocks out, we are just plainly passing this secret key here. So let's copy it from here and let's add it to our environment variables. And you can see that I already did that here because I was practicing before this chapter. So just create a new key, a live blocks secret key and paste it inside with your key like this. And then go ahead back here and replace this with process.environment, live blocks secret key and add an exclamation point at the end like that. Perfect. So now we are ready. Let's go ahead and do the following. Let's go ahead and write git add and then do git commit. and write your message. And then go ahead inside of your GitHub and create a new repository. So I'm going to call this Next14 board clone or board tutorial. I'm going to mark this as a private repository, and I'm going to click create repository. After this, go ahead and copy the second option for an existing repository right here, and paste it and run the commands. After this has been pushed, you can reset, refresh this page right here. And now you should see your repository right here. Perfect. Now let's go ahead and let's see what we have to do next. So I'm following the instructions here from Convex to deploy on Vercel. So let's go ahead and deploy something on vercel.com slash new. So I'm going to choose this latest repository that I have here, which I just added in my GitHub. and what we're going to do first is we're going to override the build command. So let's use npx convex deploy dash dash cmd npm run build here. So I'm going to go ahead and click on build and output settings, override, and I'm going to paste that here. And then we have to do the environment variables. So I'm going to go ahead inside of my dot environment dot local here, copy everything, and I'm going to go ahead and paste that here like this. But there is something we have to change here and that is inside of here we need to request our convex deploy key and we need to use the production version so let's go ahead inside of our dashboard here on convex and switch from dev to production right here and in here go ahead and go to settings and click on generate a production deploy key so go ahead and copy this and now we need to add a new convex underscore deploy underscore key, if I believe that's the correct one, convex deploy key. And we have to add that here. So you can just click add. And now if you want to, you can also go ahead inside of your live blocks and you can change from development to production, right? So you have two instances here. So you can change the production and change the API keys here as well. Generate a new secret key. let me try and generate it for some reason I cannot copy it oh so now it's appearing okay I'm just not going to do it for this tutorial but you can get the new key from there and then you can change it to production here and you can do the same thing for the clerk secret key and the clerk publishable key if you want to change them you don't have to if you just want to deploy for testing you don't have to change any of those great and I think this should be enough for us to click deploy and see if this is going to work. So I'm going to pause my screen and then we're going to see what happens. And there we go. Let me go ahead and continue to dashboard here so I can see my domain link right here. And let's see, there we go. Let me log in. And here we are. So let me go inside of my project here. Your will probably be empty because you are now in production with Convex. So you have a new database, right? I already tested this out. That's why I have this one. And look at this. Everything seems to be working just fine. You successfully deployed. Great, great job. Thank you so much for watching. And make sure to check out the website if you want additional content where I turn this into a software as a service by introducing Stripe. See you in the next video.",
  "transcript_chars": 483936,
  "transcript_filled_at": "2026-06-06T15:48:34.308364+00:00",
  "transcript_filled_by": "tk-bulk-groq-retry-20260606"
}