Build a Real-Time Miro Clone With Nextjs, React, Tailwind (2024)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey there my name is Antonio and today we're diving into the world of realtime collaboration with a project built using the newest nextjs 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 mirror clone a realtime 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 dwell 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 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 lose 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 NEX js14 react Tailwind shaten UI convex live blocks 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 Crow operations Happens Live whether you're adding a new board updating the title or deleting something you no longer need it 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 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 nextjs 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 uh 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 slint 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 uh you can customize the import alas if you know what you're doing otherwise you can just leave it exactly as it is the add sign suggest 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 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 TS config post CSS and next config before we run this project I want to add our component live library inside for that we're going to be using shat C nuui but shat cnii 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 nextjs and as you can see we already have the nextjs 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 ntx shat n-i at latest in it 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 uh 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 Cs variables select yes and that should automatically detect that we also have the typescript inside of our project and completely set up shat ceni for us great and now we're ready to do mpm runev which should which should start our application on Local Host 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 shaten 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 nextjs inside of appj inside of nextjs 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 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. DSX 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 shaty nuui 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 what how do we want to install it we can do it manually by installing the rics and then by adding this inside of our project or much simply 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 one and go ahead and run npx shuts I add 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 uh that is not the case with chatsi and UI 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 uh of course uh accessible everything which you would want in a component Library button so how do we import it go inside of page. DSX and let's go ahead and remove this and let's add the button from add/ components UI button and I'm going to write click me now let's go inside of Local Host 3000 and see that button and there we go it's right here and you can play around with it and you can uh 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 a 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. DSX 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 shaty and library to add our own components and to slowly modify them and style them uh 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 that this was a small introduction into our framework nextjs and also our component Library chat c nuui in the next chapter we're going to do a small demonstration of how routing Works inside of nextjs 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 routing inside of nextjs 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 would 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 project 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 nextjs as something special this is our convention which was made by shaty nuui once we added the button using the npx shaty Andi add button command great and the pages folder are the old way of routing in nextjs so if you ever used nextjs 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 next config.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 also going to have a middleware file but we're going to do that later middle 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 G 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 uh 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. DSX 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. DSX 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. DSX and here's what 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. DSX what I can do now is go to Local Host 3000 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 uh sub route and inside of it let's go ahead and create a new file page. DSX and let's do the exact same thing const page and let's return a div sub route 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 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 Local Host 3000 test/ 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 d damic route segment this will represent something that is not hardcoded 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 do 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 bars. 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 do PSX what I can do now is go to Local Host 3000 users sl12 3 4 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 uh 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 parenthesis 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 SL test slub 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 fold 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. DSX let's create a con page return and I'm going to write uh rendered at SL sub route and let's not forget to add expert default page and how do you think we access this sub rout now let's take a look at what this says if something is wrapped inside of parenthesis group routes without affecting the routing but that doesn't 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's 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 Local Host 3000 slub rout and there we go rendered at slub rout because we are completely omitting this uh 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. DSX let's go ahead and Export page div I am never rendered so this is my folder structure up folder undor test sub route page. 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/ sub route is a 404 how about SL test/ sub route that is a 404 as well how about slore test/ sub brout 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 paste it from here if you're confused inside of my components here I've pasted this and if I go to localhost 3000 components now this is all of a sudden a uh file route well that's not what we expected most likely if we have a folder named components that's why that's why I recommend that you hold your components in underscore components and you can see how that immediately turn this into a 404 page and you can still import use from this folder in all your other routes so it's not like they don't exist they're just omitted from the actual uh from the actual routing system and every now and then if you play around with these existing routes and change one from an underscore then to a route group with parentheses then to a dynamic one uh you're often going to see this one one unsaved file pop up which is located inside of this nextext cache that is very simple to fix you can either go directly inside and click save but if you 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 where the magic happens all you have to do is mpm run Dev and that is automatically going to generate a newex 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 nextjs a little bit so that cache uh happens great and just one more thing that I want to cover is the layout file because uh yes we have some more like parallel and intercepted routes I'm not going to talk about them in this tutorial for 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 slash1 2 3 4 5 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. DSX 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 live user list so now I can go to localhost 3000 SL 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 nov bar whenever a user is visiting the entire users 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 brows like specific user settings I always want them to have a specific Novar well layout is a perfect file to put that so we don't have to copy and paste that Novar component every single time so inside of your most top level uh 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. DSX we're going to create layout. DSX 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 SL user123 where is the user I 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 so I can see the user list on/ 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 y4 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 Navar 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 rendering so that's why these layout files are so powerful whenever you want to create a sidebar or a nov bar 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 knvb bar 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. DSX and go back to Local Host 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 dodev 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 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 nextjs quick start so let's go ahead and do that as you can see we already have the 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're inside of your project here and I recommend that you actually shut down your local host 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 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 uh my repository so I'm actually going to call it like that board video tutorial or I could 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 back end right so this is like a separate back end which we have but the cool thing is that we can write it in a mono repo right here we don't 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 going to need two commands to run this project one to run your back end which is MPX convex Dev and one to run your front end which is mpm 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 uh 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 didn't have to do that it's all automatically run and it's so fast to work with great so so now once we've done this let's go ahead and refresh our Local Host to make sure that this is still working as it should great and now let's go ahead inside of our uh Team here and there we go you can see that I have a new project here board video tutorial and you can see that is 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 MPX 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 jbt 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 board so you can see how that's going to look like it's going to say right here continue to board 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 uh so let's go ahead and copy this right here and let's create our environment. loc file inside of the project so let me go ahead and expand all of this and I'm going to create a new file environment. local oh my apologies we already have that environment. loal 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 indox and just make sure that nextjs is selected right here great so let's go ahead now and let's install this package uh npm install clerk nextjs so for that I'm going to go in side 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 mpm install clerk nextjs 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 DS and remember middleware is part of reserved file names inside of nextjs so please don't misspell this otherwise it's not going to be registered as the middleware and simply paste this in 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 would have gotten we will get an error because we didn't wrap our app inside of clerk provider but don't worry we're going to do that in a moment so just make sure that you have your middleware setup 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 uh 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 out conic out config 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 uh 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 ISS URL from the ISS 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 can stay the same uh 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 uh account like this perfect and now that we've done this to ensure that this out. 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 conx functions are ready 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 uh from clerk 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 px- client-provider DSX 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 nextext JS now let's go ahead and import convex provider with clerk from convex SL react clerk like that and now let's go ahead and let's import the following from convex 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 uh provider props which is going to have the children which are a type of react. react node so this is going to be a wrapper around our entire 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. nextext corpu convex URL and you can put an exclamation point at the end here like that so it's never undefined uh great and you can just confirm this by going inside of your environment. loal and confirm that you didn't misspell it so nextore public _ convex uncore URL like this great and now let's add a convex instance so that's going to be new convex react client and pass in 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 going to destructure the children and let's also assign the types of convex client provider props here like that and then we're 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 out loading 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 Local Host 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 mpm run Dev and 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/ 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 Local Host 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 uh 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 Local Host 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 uh and clerk to decide whether they 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 uh decide whether you're logged in or not uh we need to find a logo for our application so you can use any image you want I highly recommend that you visit logo ion.com uh they have amazing placeholder logos uh just make sure you don't use it for you know your actual business so they are completely free and open sour I'm not sure if they're open source uh 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 uh you learn 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 we're going to create uh 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. DSX and let's import the image component from next SL image and then let's go ahead and Export con loading and let's go ahead and return a div with a class name of full width full height Flex Flex call Gap y for 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 uh 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 a source of/ logo.svg let's give it an ALT 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 in ation 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 uh inside of our providers convex client provider and now we can use out loading 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 that if we are out loading then render the loading from components out loading so make sure you have out loading and authenticated imported from comx react and make sure that you imported loading from at/ components out loading which is the component we have just created so let's see if we need to modify anything I'm going 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 can every time you refresh you should be seeing this loading component here uh 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 log out button so it's kind of weird we don't even know whether we're 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 culprate is so in here we use the full height and full width but I believe that inside of our app folder global. 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 global. CSS here and add HTML comma body and comma colum root and add a height of a 100% 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 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 going to uh make this one below another so I'm going to do this Flex call Gap y4 so it really doesn't matter right we're going to 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 nextjs 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 Local Host 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 uh customization options right here for example branding you can actually add any logo you want so right now when I log out uh my screen just says you know log in to continue to bardy which is my application name but you can also add a logo so I'm going to add our logo here uh it needs to be smaller than okay basically you can try and add a logo I picked the wrong image now uh and then it that logo is actually going to appear uh 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're 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 uh when they show us uh the pro features without as 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 uh and most importantly what we achieved here is we connected our convex which is a uh real-time database which has you know some custom API typings which you're going to see in a moment and we've completely secured convex backend and our nextjs application at the same time so that's why we needed to do the whole jbt 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 wordss and a knf bar 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. THX inside of a route group folder so I don't want this page. THS XX 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 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 uh nextjs 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. vsx completely and then inside of the app folder create a new folder dashboard like that and then inside create a page. CSX and this is now going to become our new root page. DSX so this is the exact same thing as having page. DSX 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 or organization sorry for our dashboard route group so inside of dashboard create layout. 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 con 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 root page which we defined right here now let's go ahead and create our sidebar so for that we're going to go into inside of the layout. vsx and we're going to replace this wrapping div with a main element like that and then we're going to give this main element a class name of H full and then in here I'm going to render a sidebar component and I'm going to go ahead and wrap the children inside of a div and all I'm going to 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 this 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 uh another folder called sidebar and let's go ahead and create an index. the SX inside of here and very simply let's just do con sidebar actually export con 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 do/ components slidebar like that perfect and there we go you can now see that we have a little side bar here so let's go ahead and style this sidebar just a bit so go back inside of components sidebar index. DSX and the reason I didn't just create a file named sidebar uh 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 blackle skull and GAP y4 like that then in here this will represent our sidebar so you can add a little Plus for example uh 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 H4 and that is also going to wrap the children so just make sure you add children inside of this new div right here uh 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 Navar 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 uh 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 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 6 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 some 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 uh 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 _ components org sidebar this 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 now is we have to create a nov bar and then that is going to push this dashboard root page in this content right here and then we're are finally ready to start working on this sidebars and nav bars 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 our navbar component so let's go ahead and do that inside of this underscore components so new file navb bar. DSX 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's export con Navar inside of here and let's go ahead and return a class name of flexx items Center Gap X4 and padding of five and you can also give this a BG uh let's let's use green 500 so we can easily see it and then you have to go back inside of the layout here and where you add have a comment add Navar you can now simply render the actual Navar from the same place do/ components Navar and there we go you can see how this looks on mobile and let's also add a little text here so Navar like that there we go so you can see this is how it looks on mobile and this is how it looks on deskt stop and yes there is a little space here but that is completely okay because the actual background color of the Navar 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 nav bar so let's go ahead and simply render the user button from clerk nextjs right right here there we go perfect so now it is right here and you can also uh 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 uh and on large it's going to be Flex one like this and on large is also going to be Flex by itself and then in here let's wrap the user button to be class name uh 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 on mobile modes 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 going to 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 uh when this appears or perhaps we need to give it some padding I'm actually going to 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 uh to switch organizations and to uh 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 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 take 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 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 rle let's also add the organization ID and you can either write it manually or you can click or. 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 in inside of the organization if you want to do even uh 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 shat 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 cnii at latest add dialogue which is going to add a new dialogue component and besides that I also want to install a tool tip 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 shatsu latest add tool tip as well so we are adding a dialogue component and a tool tip component and once those two have been installed you can go ahead and run npm runev and every time that you shut down your app and then run mpm Roden 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 in 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 Dev like that uh 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 uh sidebar and inside of here uh we are not going to render side instead we're going to render new button component which currently does not exist so inside of sidebar create a new file new- button. 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 nextjs and then I'm going to go ahead and import from components UI dialogue which we've just added a dialogue a dialogue content and a dialogue trigger so we have something to open it great and now let's just export con new button here and inside of here I'm going to go ahead and return a dialogue let me just invent this properly and I'm going to go ahead and give this a dialogue trigger which will have as child prop and inside of here I'm going to add a div with a class name of aspect Das 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 on 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 sl25 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 it 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 withth 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 organiz ization excellent and this is how this class name looks so max with 480 pixels border num transparent and padding of zero perfect so before I create uh my new um 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 nextjs let's go ahead uh and Well for now let's export cons 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 going to write if there are no user memberships length sorry data. 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 do dat map and let's go ahead and get the individual membership and inside of here I'm just going to render a paragraph and let's render the name uh membership. organization. name and let's give this a 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 do/ list so we have a list and new button great so now uh 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 uh 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 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 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 uh now what I want to do is I want to display them in a better way and I will also want to indicate which organization is currently selected which one is currently active so for that we're going to have to create uh 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 use client as well and in here let's import image from next SL image and let's go ahead and import from at clerk nextjs 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 shat CMI 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 uh 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 here we can destructure the name uh 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 going to 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 onclick 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 Bill 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 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 act so sorry we're going to fine is active to be the current organization which we get from this hook question mark. ID is equal to the ID which we are passing right here and then then let's do const on click here if we are not act 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 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 do/ 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 relate to the sidebar and now we have to pass a couple of props here so let's give it a key to be membership. organization. ID let's repeat the exact same property but for the ID prop let's give it a name of me membership. organization. name and last one is going to be image URL and the prop for that is going to be image 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.msi remote patterns protocol is going to be https and then host name is going to be whatever is written for you here so I believe it's going to 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 uh 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 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 uh 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 tool tip component to add the name once we hover but it will be nice if we could somehow generate the initial uh initial of the name of the organization to indicate the user which one is which where clerk can do that so you can go inside of clerk right here and let's go inside of uh 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 initials and there we go you can click apply changes and now all of your organizations are going to have initials instead of uh those boards if you want to you can actually remove this organizations and then create new ones or they're going to maybe be upgraded a bit later let's try this with 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 uh yes and also I don't know why exactly when I finish creating an organization and when I click skip here it seems like nothing nothing is happening so we're going to debug that later but for now you can just manually close uh okay so what I want to do now uh is I want to create a hint component so that when I hover over any of these elements I can see the full name of the organization and also when I hover on this I want a tool tip to tell me exactly what uh 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. DSX and in here I'm going to go ahead and import everything we need from at slui sorry components UI tool tip so we need the tool tip itself we need the tool tip content we need the tool tip provider we need the tool tip 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 we're going to have side which is going to be an optional top bottom whoops 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 off set which is going to be an optional number and a line offset which is going to be an optional number as well and then let's go ahead and expert const 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 a line offset perfect and now inside of here I'm just going to return the tool tip provider and let's make sure we close this tag inside I'm going to add a tool 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 tool tip uh 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 not hydration errors either and then let's add tool tip content here and inside of the tool tip 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 tool tip 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 align and also side offset of side offset and Al line 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 uh 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 SL hint which we've just created and let's give it a label uh of create organization like that and it says that hint cannot be used as a jsx component oh I believe I forgot to write 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 or 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 line 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 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 going to 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 want to go ahead and I want to create this organization sidebar component right here so in order to see that you're either going to 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're developing and also I didn't for get 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 uh changing our layout a bit uh 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 knv bar and start creating some boards here so let's go ahead and find where we have organization sidebar so that is inside of dashboard undor components organization DS sidebar. vsx 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 popins from next font Google and let's leave it like this for now and now let's initialize the font using the popins 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 back BG color red I no longer need that we know how this is supposed to look like now and inside instead of rendering or sidebar I'm going to render a link component which we added let's give it uh an hre property of an empty uh 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 SL logo.svg so basically the same thing that you use inside uh of the loading component right SL logos 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 semibold and text to Excel and then we can add font do 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 uh 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 clerk nextjs so just make sure you add organization switcher from Clerk nextjs and that's going to be a self closing element like that and you should be seeing something here so let me zoom in there we go you can see how now we can switch between uh different organizations and we can also create an organization from here so what I want and we can also trigger this manage organization uh 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 uh 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's 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's focus on the code for now so we're going to Target the elements we're going to targ the root box we're going to give it a display property of flex we're going to give it a justify content property of Center align items property of Center as well and width of 100% so I don't know if you knew that but you can style clerk components both the login log out 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 uh organization sidebar because right now it looks fine but I want it to have a bit of a border right it just looks too plain uh stay 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% a border radius of 8 pixels and let's give it a border of one pixel solid and let's use E5 E7 EB color uh also I have this little extension here if you want the ring uh is 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 uh intellisense which is quite important if you're working with Tailwind uh it helps a lot uh all right and let's add justify content to be space between and let's add 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 uh 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 HRA 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 withd four and margin right of two and I'm just going to write theme boards here 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 an as child prop let's give it a size of LG and let's give it a class name of font normal justify start padding on xaxis of two 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 going to uh 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 uh a different link for these 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 uh 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 uh 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 favor is true here and if I go back it's removed so depending on that query I want to highlight a specific button to indicate the user all right you're currently looking at this one so let's go ahead and let's get our search params which we can import from uh next I think it's just next uh wait it's next SL 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 pams and let's get the favorites from search pam.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 uh in the URL so variant here it's not just going to be hardcoded 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 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'm 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 ghost looks better so I'm going to bring it back to ghost uh great great so now we have a way to switch our organization you can see how the opacity changes in the nav bar in the sidebar as well perfect and from here yes we can also manage uh 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 now when I 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 in the the settings of the individual organization and I'm going to give a rooll 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 uh inside of your user and authentication make sure you have email address enabled if you don't have this then uh 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 uh 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 uh 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 nov bar 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 dosx 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 going to go ahead and do that inside of the underscore components here so search- input. THX it's going to be a client component and let's also export cons search input here and let's return a search input like that and now go back inside of the Novar and instead of the text search you can add search input here from do/ 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's go ahead and let's install query string and let's install use hooks DTS 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 per Rams 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 debounce from use hooks DTS which we've just installed we need use router from next SL navigation whenever you're importing use 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 now let's go ahead and import everything we need from react so that's going to be the change event uh we're also going to use effect and we're going to need use State and one more thing that I forgot to add is the actual input component so npx sha CNU at latest let me just expand this as much as I can add input like this so we need an input component let's do npm run Dev again let's refresh Local Host here and then what we're going to do is we're going to give this div a class name of full width and a relative and let's add a search icon here and let's add an input here and you can import import uh 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 full 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 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 Dy 1 and [Music] A2 text muted foreground height four and width four 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 uh 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 add 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 bounce 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 be 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 con 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 are very simply going to set the value to be event. target. value and then let's go ahead and write 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. stringify URL the URL is going to be slash and the query is very simply going to be search and pass in the the bounc 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 null true so we are 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 onchange and pass in 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 Navar component right here and right here uh 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 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 uh 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 nav bar and paste it here and you can import the organization switcher from clerk next tojs as well there we go and let's just confirm that everything can stay the same so display Flex makes send justify content Center makes sense align items Center makes sense the wi it 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 uh 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 uh 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 Das button. TSX and let's go ahead and import plus from Lucid react let's import organization profile from Clerk nextjs 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 dialog trigger and let's also import a button component from components UI button and then let's export con 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 cnii 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 four width four 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 Navar component we can add this after our uh div wrapping the organization switcher so I'm going to add invite button from do/ invite button like this so the same way we imported the search input and there we go 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 if they are not in any Organization for example if I go here and if I remove this organization 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't know to which organization are we supposed to add someone to so what we're going to do is we're only going to display that in the nov bar if we have an active organization and we can do that using the use organization Hook from clerk nextjs so let's get the use organization Hook from here and let me just collapse this elements 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 uh 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 uh I think I need to destroy structure the organization my apologies yes so 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 uh 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. THX 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 the height of the nav bar because we reduce 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 uh GitHub repository go inside of public and find elements. SVG like this so this is a part of uh 3D illustration pack called Sally I'm going to leave the link in the description for the full pack as well and it is uh allowed to use in both commercial and personal projects and now just uh 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. CSX 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. DSX 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 do/ components empty organization and there we go now we have a text which says empty and now I want to go ahead inside up here and I want to give this div a class name of H full Flex Flex call items Center and justify Center like that and now inside of here I want to render an image from next SL image with a source of/ 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 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 text small and margin top of two to 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 nextjs 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 dialog trigger like that let's go ahead and uh 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 prop so it doesn't mess up the Styles or cause hydration errors and we're just going to say create on 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 edit dialogue content and let's render the create organization component and let's give our dialogue 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 users first time joining and they haven't created been invited or selected any category so uh sorry organization so then that 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 ation 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 uh 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 uh react server components in this tutorial as much as we focus it uh focusing that in other tutorials so this one is going be a bit for more familiar for the single page application folks and in here let's add use organization from clerk nextjs 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 end 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 uh 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 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 uh 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 forams 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 are trying to D structure 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 assign that here so dashboard page props search forams 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 uh 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 going to add a board list component and we're going to go ahead and pass in the organization ID as a prop to be the current organization. ID and the query is going to be search params so we know uh how to load the data and then let's go ahead inside the underscore components and create a the new file board- list. 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 con 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 Jon stringify query one more time to confirm that this is working so I'm going to go back inside of my dashboard page. 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 on 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 different empty States so so if we don't have data length like that but we have query. search that means that the fact that the data is empty is because user searched for something that doesn't exist so we can change our return to say s uh 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 uh have query. favorites in that case we can return IDE saying no favorites like that and we can also use the uh 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 do favorites and if you don't have query. search but it's just easier to make it 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. 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. 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 uncore components right here I'm going to create a new file called empty search. 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/ - search. 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 FX Flex column items Center and justify Center like that and then we can head back inside of our uh 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 we what will appear but if you remove it then it's going to be a different empty state if you go to favorites in it is a different empty State again great so now we have the boiler plate for our uh 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 import 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 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 uh 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 and 10 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 uh 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 do/ empty boards and there we go we now have three uh empty States the first one is if we don't have any board at all so we're going to 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 are 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 wordss 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. DS 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 SLS server like that and then let's go ahead and Export default Define schema here and let's add boards to be defined 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 Alor name as well and we're also 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's also add filter fields to be organization ID because we only going to search inside of a single uh organization GD 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 and I can click on so 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 uh 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 my repository or simply go to andraw 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 dro all of these files here so make sure that you have at least a couple of them the 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 uh 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 andraw so I'm going to 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 our an actual API route which are going to be which is going to 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. DS and this is our new API endpoint and then in here we can do export cons create and call a mutation and we can import mutation from slash generated SLS 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 passing 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 identity full authentication inside of this convex uh back end which you can see is not in route handlers but in its own folder so that's why we had to do the al.cc config.js with the template and all of those other configuration elements perfect so now I want to create a little uh array of images here and what I'm going to write right 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 slash1 SL2 3 4 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 uh 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 con 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 ar. 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 name and let's also pass in the image URL which is going to be 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 uh which will fire well we have to create a functionality that when we click click on this button it actually calls this API mutation and then we can going to go we're going to go ahead inside of uh 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 going to be we can get that from organization right here using use organization from clerk nextjs 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 do 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 uh use mutation from convex react and make sure that your typings are correct api. board. create you can see how I can click here and it will lead me to that mutation right here uh and also you can always check your terminal just to confirm that there are no errors but in your convex functions right here great so we have that now uh and you can see that immediately once I click here uh 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 uh and create just a new folder in the root of our application called Hooks and inside I'm going to create use API mutation. THS let's go ahead and let's import use state from react let's import use mutation from convex react and let's export cons 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 mut ation let's pass in the payload let's add do finally to Simply set pending back to false and do then we'll 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' have to do do finally here and change the pending back to false and we would have to do that for every mutation that we do do what we can do instead is now use the following use API mutation from our 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 disable 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 these custom abstractions around hooks so you learn something this kind of reminds me of using uh 10stack query mutation hook where we have you know mutate and depending State here if you don't like it if you think this is a too big of a tradeoff by using any uh 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 uh our our boards let's go ahead and add some toast notifications so it indicates to us whether this was successful uh or not we can do that by going inside of our terminal here let me just open a new one and the npx chat CNU F latest add sonor like that so that's going to add the sunor toast package inside of our project so we now have uh one file to add which is called toaster so going inside of the app folder layout. TSX and let's import toaster from components UI soner and then we're simply going to go ahead and render that well you can just render it inside of the body or 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 soner like that we're going to do toast. success board created like that and I'm going to add a to-do redirect to Bo slash ID and then let's add. catch and this one is very simply going to call post. error something actually let's be more specific fail 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 uh in a second when 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 of our uh 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've wrote so it's very very easy to debug things in production it will be amazing if we had this for you know 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. THS 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 uh 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 that we should be having uh 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 Chron jobs and a bunch of different things uh 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 perf perfectly into what we're trying to create here as a miror 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 to 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 SL 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. getet user identity if we don't have the ID entity 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. databasequery 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 and let's do query equals organization ID arguments. 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 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 Ed client is we can replace this data from being an empty array to instead be used 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 add a uh I'm going to open a parenthesis right a div 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 uh 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 going to write uh 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 uh great now inside of here let's open up a div and let's 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 two 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 our data. map we're going to get the individual board and we're going to go ahead and render a board card element which which currently does not exist but we're going to create it in a second let's pass in the key to be boore ID let's pass in the ID to be boore ID as well we're going to do the same thing with the title so board. title let's go ahead and pass in the image URL which is going to be board. 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 propop which is going to be board doore creation time and let's also pass in the current organization ID for that from 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 uh create our our board card element here so I'm going to create a folder for that because it's going to have some other uh 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 con 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 do/ boardcard 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 uncore components and there we go you can see that uh we now have board cards in a grid like this great so now let's go ahead and let's fix these 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 althor name which is a string althor ID created at created that 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 bullion and now we can go ahead and assign all of those here so board card props and we can safely extract uh all of those auor 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 HRA is going to go to slbo SL 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/17 Border rounded large Flex Flex call col so all the items inside are one beneath another justify between and overflow hidden and now let's go ahead and add a div here with a class name of relative Flex one MBG 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 placeold folders inside using the image component from next SL 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 uh which were created every time we clicked on uh create a new board so if you're seeing some empty images uh go inside of your database here and just confirm exactly uh what you're seeing inside of image URL so you can expand it like this and confirm that this placeholder SL1 SVG 827 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 in 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 uh and now instead of having alt of doodle I think it's better that we actually give the title of the board I think that makes more sense uh 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 con 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 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 do/ 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 uh 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 uh from when it was created so let's go ahead and install date FNS for that so mpm install date FNS so we can then import the package that we need I'm going to go ahead and import format distance uh to now from date FNS like that and in here let's go ahead and let's define const althor label is very simply going to check if the user ID uh which we can very easily get from use out and you can either import it from Clerk nextjs or you can import it from convex react but I'm going to be using clerk nextjs so you can try out both those so use out clerk nextjs 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 youu 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 uh our footer component so the footer component is going to go uh 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 auor label which we've just created in the 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. DSX element like that and let's go ahead head 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 outdoor label a created at label is favorite which is going to be a bullion on click which is going to be an arrow function and a disabled prop which is going to be a Boolean as well and now let's export con further and let's very simply assign those props and we can extract the title outter 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 footer now we can go back inside of the board card and we can import the footer from do/ 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 althor 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 trun Kate 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 uh 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 uh below that let's add another paragraph which will use the Alor label comma uh 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 one 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 we won't be seeing the information unless we hover on it and then we're going to see some more information about our boards beautiful and all of your boards should have the Untitled title because that's what we that's the way we wrote Our code for now uh all right and now let's go ahead and let's actually create this little button here so it's going to render the star icon which we've imported from Lucid react and let's go ahead and give it a disabled of disabled let's give it an on click off on click for now uh and my apologies we're 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 going to be zero but on 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 Matrix 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 find the width and the height of the icon and then if we are already favored this we're going to add fil blue 600 and text blue 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 uh let's go inside of inside of board list right here and change is favorite to true and now if we coded this correctly this should change to a fi 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 to 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. TSX let's mark this as use client and let's export con 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 D and disabled 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 on click 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 CN util from Li utils so if it is not disabled it's going to have your us 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 going to be blue 600 rounded LG and let's also add while we are here uh on Hover but specifically on this board hover it's going to be BG blue 800 so it darkens we already added rounded LG that's good so besides that we're going to need Flex Flex call items Center justify Center and P 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 H2 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 uh this setup right here and I forgot to add the actual Dynamic CL class here so if we are disabled in that case opacity is going to be 75% like that so let's try it out new board like this there we go I think this looks fine uh 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 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 in API board create and let's 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 uh great and now uh let's go ahead and try it out so if I go ahead and click again there we go you can see how it's disabled for a quick second and now let's just add the toast notifications so toast from soner 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 to. success board created and I'm going to add a too redirect to slbo ID and let's add. 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 uh 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 shat CN UI at latest add skeleton so skeleton like this and then let's go ahead and let's actually import this 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're trying to replicate what's actually going on but we don't need uh 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 going to change this manually to always be true so that we can always see the loading text in here so now we're going to change that to actually display our elements so for that I want to actually copy uh 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 be it can work like this query. favorites favorite boards or team boards and then this div is just an grid and inside we are going to render the new board button but we're going to make it disabled so pass in the organization ID and also pass in theable and then in here we're going to pass the board card do 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 shimmers 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 uh new board button and we're also going to add on here hover BG 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 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 uh to load uh 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 uh and figure out the favorite board Tab and how we're going to render only those and then we're going to wrap it up with 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 shaten UI called drop down menu so let's go ahead and run MPX shat cn- UI at latest add dropdown-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 dialogue 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 dialogue which we are going to need and then we're going to install to so npm install two or tand I'm not sure how to pronounce that so make sure you have drop down menu alert dialogue and tand and then let's do npm uh actually I think I already have it running let's see npx convex Dev and npm run de great I have both of those running so I can just refresh this to confirm that there we 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 uh 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. ESX 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 uh content props which you can import from radx 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 radx because we added that using shat nuui so we are only importing the props from radx we will not be using the actual component from radic 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 boardcard index. TSX right here and we're going to go ahead and put this just below the overlay we're going to add our Act action 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 going to pass in a button component and then inside I'm just going to 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 one right one there we go okay so we can Vis 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 going to do now is we're going to go ahead and actually render this uh children right here so this button which we are supposed to see so right now we are not seeing that we are seeing this text actions so we're going to go ahead and import everything we need from the actual component UI drop down menu but not from radics from our components so we're going to need the drop- down menu the drop down um 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 drop- down menu and then let's add a drop- down trigger inside where we're going to just render the children and let's give it an as child props so it actually shows that button over there uh 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 uh this is where that is going to 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's 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 one right one opacity zero and group hover opacity 100 transition opacity PX 3 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 uh 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 guess the event and anywhere where I click in side I'm going to stop propagation so that I'm not redirected uh 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 four width four and margin right of Two and a text is going to be copyed board link and let's give the drop down menu item itself a class name of padding 3 and cursor pointer so let's see if this improved anything so now when I click here there we go you can see how I have an option to copy the word 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 uh 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 achieve that by using onclick event stop propagation on the drop down 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 uh if you think there is a better way to do it uh 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 do clipboard. write text 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 slbo and then the individual ID and then we can add do then here because this is actually a promise so in here we can call our toast from soner 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 soner package so just don't forget to do that uh and let's now add the uncopy 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 tost 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 new error unauthorized like that and then what we're going to do is wa context database delete and very simply we're going to pass in arguments. ID so we don't need to specifically say uh to delete from the boards because the ID is type of ID boards so convex knows that we are deleting uh 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 a another drop down menu item so I'm going to copy and paste this one and I'm going to go ahead and use the icon uh trash to here from lucid react and the text is going to 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 uh remove is because I don't want to take the reserved delete keyword in JavaScript so make sure you import the API from at/ convex generated API you can use my use API mutation or I already showed you uh you can use use mutation from convex 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 the delete which is very simply going to call mutate passing the ID which we have PLL do then tost success board deleted and do catch tost error fail 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 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 want to do now is I want to 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 going to 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 . DSX let's import use client and let's go ahead and import everything we need from components UI alert dialogue and that's going to be alert dialogue itself alert dialogue action alert dialogue cancel alert dialogue content description we're also going to need the footer the header the title whoops so alert dialogue title and alert dialogue 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 unconfirm function which is going to be an empty void disable prop in case we are doing 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 dialogue then the alert dialogue trigger which is simply going to render the children and have the as child property and then we can use the alert dialogue content which needs to be closed here and inside let's add the alert dialogue uh header alert dialogue title and in here we're going to render our header prop like that and then just outside of the title let's add alert dialogue description in where we're going to load the description prop like that and then outside of the header let's add a alert dialog footer component and in here very simply we're going to have the alert dialogue cancel which is just going to say cancel and below that we're going to have the alert dialogue 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 H handle confirm is very simply going to call the on confirm option like that there we go that's it that is our reusable uh 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 confirm model or/ components confirm model however you prefer it and then you can can very simply just wrap the entire drop down menu item in confirm model like that I believe that we can do it like this uh and what we have to do now is we have to pass in uh 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 the board and all of its content 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 drop down menu item because I believe this will now not exactly work so let me just uh cons log comment out this drop down 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 uh as a child of the alert dialogues so instead what we're going to do is we're going to use the normal button here so you can just import button from do/ 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 uh and give this a text small a full width justify start and font normal so it doesn't differ from our drop down menu item component and I think that now we're going to 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 uh 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 uh the context and the arguments so in here let's go ahead and let's get the title using arguments. tile. 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 what we are pring it so that the user cannot pass empty whites space as characters uh 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 uh throw new error unauthorized all right and now let's go ahead and simply do to 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 uh 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 function it here uh 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 form model which I want to control in a different way more specifically I want to control it using two stand hooks so let's create a new folder in the root of our application called store and inside create use rename model. DS and let's go ahead and import create from suant 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 onclose function which is going to return a void with no props inside and now let's do export con username model to use the create let's pass in the IR rename model interface here and let's get the set in the 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 onop to get the ID and the title and that's going to go ahead and call the set function in where we're going to 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 uh a basic model to rename uh to rename name 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 going to store those kind of models which are reusable throughout and let's go ahead and create rename model. bsx 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 it's going to be controlled with a tand store which we are going to programmatically open depending on how we need that so you know your 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 dialogue so in here I'm going to add a dialogue dialogue content dialogue description dialog header dialogue close dialogue footer and dialogue title and then we're going to go ahead and do export con rename model and as opposed to how we did it in the confirm model we are going to heavily rely on our username model from store so inside of here we're going to go ahead and extract it open on close and initial values and then what we're going to do is we're going to return our our dialogue 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 close so that will programmatically change this one to closed and let's add the dialogue content here dialog 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 nextjs when we use programmatic control of our models using something like tand um 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 provider 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 for 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 a 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 uh client side rendering it just means that it's not a server component and server component is not the same thing as server side rendering those are two different things components which are marked as used 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 going to 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 to oppose 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. DSX 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 onopen function from the use name 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 onop 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 uh the third option here so I'm going to go ahead uh and copy the drop- down menu item here let's go ahead and let's paste it just below the copyboard link and this one is going to be uh 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 uh 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 uh tand I actually do that a lot in my tutorial so it might be even more familiar for some of you if you've been watching my tutorials but you're welcome to experiment yourself uh 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 uh 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 going to 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 uh and actually write the logic for it so I'm going to go ahead here and let me just add my title and set Title Here to come from UST state from react and I'm going to pass in the initial values so I need to do that uh below the username model my apologies because we need the initial values so initial values. tile like that let's add a use effect from react which are going to watch for the changes of initial values. title so if we open up a new one and we're going to do set Title Here to be the new initial values. 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 uh let's go ahead and let's add Con on submit here to just be an empty Arrow function for now and and now let's go ahead uh just below our dialogue description let's add our form element and let's add our input from do /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 uh 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 called 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 putter component my apologies inside of the form like that so in 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 shat sorry not native the shaten button so just make sure you add this import 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 the form and the variant of out line and then we're going to have a dialogue uh we don't need a another one we just need a button which will say save this one is going to have disabled of false for now which we're going to change the pending 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 uh give a little space in between these two buttons here so I believe we can go to directly to the form here and and let's go ahead and give it onsubmit to our onsubmit 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 use API mutation hook so hooks use API mutation and I'm going to pass in the API from convex generated API board do update like that from here I'm going to be able to the structure mutate and bending 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 form event from react so import form event 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 onsubmit 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 def F 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 controlled and let's add then here and let's call toast from soner so make sure you import this I'm going to move it here with the Global Imports so toast. success is going to be uh board renamed and since we programmatically opened this we also have to programmatically close it so we're going to call on close here and let's call do 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 username model usually when I work with sand I like to do this state state but I think I don't have to do that I I 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 uh 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 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 3 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 uh the additional logic to query by favorites into 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 a404 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 and 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 favor it 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 bu 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 uh the way convex works is that it kind of allows you to do both SQL and no SQL kind of schema so you technically could have made you know uh favored users and add and make this a do array of v. string which would represent something like an array of user ID like that the problem is um that is not exactly scalable right and they themselves don't recommend doing that if you have more than you know 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 favored 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 uh 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 con 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 VI ID boards then it's going to have an organization ID which is a type of VI ID uh 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 an authorized now let's go ahead and try and fetch the board where trying to favor it so const board is going to be a wait 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 a wait 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 uh board ID to match boore 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 that unique yes I think we can also use that unique and then this will be just a single element right so if you used 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 unique we're going to see if this is 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 uncore ID and organization ID to be arguments. organization ID and lastly we can return the board uh like that or we can do H yeah we can just return the board it doesn't really matter what we return because we are not going to 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 going to copy and paste this entire thing here and just below that I'm going to paste it and I'm going to call it unfavorite so it's going to work the same way first let's fetch the identity like that let's get the board let's get the user ID here here and in here uh what we're going to 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 squarey 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 favor board not found meaning that you cannot unfavor it something that you didn't favor in the first place place and then instead of insert we're going to use delete then that's going to be existing favorite undor 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. organization ID uh 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 the this index so we can make it even simpler by removing this and using by user board let's go ahead and check my schema so we have by user board yeah so I don't think we need byy 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 check if or ID needed so I think we don't need it I think we're going to use organization ID 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 uh changed it to use a simpler index so I'm using by user board instead of a bu 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 so let's go ahead let's continue for now and let's just see perhaps we can also simplify the original existing favorite Den I might think we'll see okay let's leave it like this for now and now what I want to do is revisit my app folder dashboard unor components board card right here and inside of here where we have our footer we have to create this onclick functionality which is going to toggle between favoriting something and unfavor something so first things first I want to go ahead and I want to import my use API mutation from hooks 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 uh 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 the structure 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 is going to have pending state which we're going to rename to pending favorite this one is also going to have a mutate of un unfavorite and a pending state of pending unfavorite like that and then what we can do is create con 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 or if ending unfavorite like that uh and now here's the thing so we are hardcoding the is favorite so right now whenever I click on this uh it should give me uh it should give me the create action right so here's what I'm going to do we don't need a success message for this because it's going to be immediately shown to the user that something has been favored later when we implement the actual logic to to load that but but I do want to get the errors so what I'm going to do is add catch toast from soner error failed to un favorite and I'm also going to go ahead and add a catch for on favorite here so we know if something goes wrong so fail to favorite and make sure you import the toast from soner itself and let's try it out now so now I believe that my user favorit is completely empty here but if I click on one of this right here uh 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 um 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 uh existing in the scope and what I'm going to do is event stop propagation and event prevent default and then I'm going to call on click and then use this handle click instead of direct on click here when we favored something and 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 favor it again something that has already been favored but if I click on this let's just wait for this to hide if I click on another one then 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 favor so what I want to do now is I want to modify my main uh board my main convex boards function right here so that in the end I also include a field is favor 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 uh where were we we were in the board card index so if you want to you can use the alternative you can use const uh hand favorite and use use use mutation from convex react and pass in API board favorite and then when you call handle favorite here you're going to have oh you can see that you're 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 use mutation from convex react if you are confused uh and then you can check your types more thoroughly great so let's go ahead now uh and let's go inside of the boards here and what I want to do is I want 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. databasequery user favorites Within index by user board let's get the query I'm going to send this to new line and I'm going to call uh query do 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 let 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 uh 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 uh should 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 his favorite false to where we are itating over our board cards and 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 unfavor it if I click here I unfavored it again and you can see how I'm no longer getting any errors because now our function knows when it should favor it something and when it should unfavor it 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 uh Native use Query from convex react I thought this would be maybe a fun exercise but I can see how with this any typings uh some of you might be losing you know the idea of what actually needs to to be passed there uh but yeah you can see that obviously this is still working but if you're getting confused you can go ahead and uh 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 but 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 on favorite is going to be use mutation API word unfavorite I'm getting the use mutation from convex react and then in here I would very simply called 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 uh I should be able to import for this ID boards oh my apologies so id id as the type ID which you can import from ATC convex generated so let's go back here there we go so import ID from at/ convex generated data model and then when you're using that ID specifically you can specify even further that this should 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 why 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 any enabling the search and querying by actually favor 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 favor but I don't have a proper 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 and 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 do equals user ID to be user ID and equals board ID to be uh the arguments. ID like that and let's go ahead and get unique from this uh combination and then I'm going to write if existing favorite exists let's make sure to remove that relation so await context. database. delete existing favorite uncore 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 board I have a new board and I don't have any user favorites here one 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 uh let's go ahead and let's try one more thing in our favorite function I think we can speed up the 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 commment 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 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 con 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. 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 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 uh 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 can 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. DS and let's also add favorites to bb. optional and let's go ahead and Mark this uh as v.n like this actually I think we can make it uh 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 uh 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 ARS do favorites in that case we're going to do con 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 favor boards and now let's go ahead and get all the IDS by using favorit boards. map and let's go ahead and extract board ID sorry bore 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 so I'm going to go ahead and shut down convex for now and let's do npm install uh convex dashh 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 SLS server SL relationships like that so we don't have to write any complex quaries here instead what we can do is const board to be await get all or throw context. database and simply pass in 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 we are only loading the wordss uh by the favorite wordss IDs right here and I made a mistake here so this will actually not give us the result we want so the I list of IDs that I wanted to make was the list of relationships inside of our schema for we can 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 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 uh complete search querying and everything that we need here and what we are finally ready to do next is build this actual screen uh 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 components and new board button so let's go in empty boards first let's see where it's located app folder dashboard uh components empty board so we can now actually do this redirect right here right we know how it's going to look like so constr 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 Tod do comment and the other one which we had let's see if we have 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 CL 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 head and remove all of my elements here so we can confirm uh that it is actually working from that empty screen as well we confirm 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 nov bar 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 Bo 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. DSX 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 cons 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 H 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 d saying info now let's go back inside of the canvas and we are very simply going to import that info component from doino and now let's go ahead and actually give this info component a class name of absolute top to left to to BG white rounded MD PX 1.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 component which is going to be called participants. TSX so let's go ahead inside of participants here and let's very simply export con participants which is going to be very similar so let's go back inside of convas and Below info add the participants from do/ participants and now inside of here we're going to have we're going to go ahead and write class name absolute height 12 top two right two background white rounded MD padding of three 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. DSX let's export con toolbar here toolbar like this and let's go back inside of the convas component component and let's render the toolbar from do/ 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 two a flex Flex call Gap y4 and then inside I'm going to create a div with a class name of BG white rounded MD like this padding 1.5 Flex Gap y1 Flex call items Center and Shadow medium and in here I'm going to write uh 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 ellipses 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 uh inside of here great so just before we wrap this up let's just go ahead back to Local Host 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 going 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 face faes that is only inside of this organization if I go back to the other one then it's Untitled perfect so 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 uh app folder board board ID page. TSX and let's go ahead and create an interface board ID page props to have Rams which our 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 pams and let's also prepare the canvas to accept the board ID prop from pams 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 convas 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 live blocks so use the link in the description or go to live blocks doio 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 live blocks client so now let's go ahead and let's go inside of our terminal so we can set up live blocks 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 live blocks so let's go ahead and run mpm install at live blocks SL client and after that let's go ahead and install live blocks SL react so let's wait a second for this to finish and then live blocks SL react after this has been installed we're going to be able to run an npx command to initialize the live blocks. config.sys so for that run npx create live blocks Dasa at latest and then let's write-- init D- framework and then react so let me zoom out and try and show you this in one line so npx create live blocks app at latest D- init D- framework react all right and let's run enter and that should set up our uh live PL box config so just confirm the newest version if you get this prompt uh 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 live blocks config TS has been generated so now you should be able to find this file right here live blocks. config.txt uh added here and it's expecting an actual public API key so let's go ahead for now and remove the out end point 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 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 room. DSX let's mark this as use client and let's go ahead and import react node from react let's import room Provider from /live blocks. config and let's go ahead and import client side suspense from live blocks react I'm just going to keep these two separate because this is our Alias path to a file right which we have right here live blocks. config and in here you can see everything that is uh exporting like a room provider which we are importing here and this is an npm package so that's why I like to separate this Imports because this is an alias to my file and this is an npm package and 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 f back 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 uh board board ID page. TSX right here and now we're going to go ahead and import room not from live blocks 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 uh pams board ID like that so now if you take a look in your uh live kit here rooms I'm not sure if we're going to have any rooms there we go we have no rooms yet but I think that just by refreshing this page after we run npm runev 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 Local Host 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 uh board ID from convex so this way we synchronized live blocks with our convex database uh in that way by using the same unique ID so we know which which live blocks room relates to which canvas uh board perfect so if you go ahead and click on some other boards that you have that's going to go ahead and create uh a new live blocks room for you so right now I only have one room but if I go to some other uh organization right here there we go now I have two rooms and it uses the ID of that board you can see the 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 F back here and I'm going to pass the fall back like this all right no errors here but now my page should have an error and in here instead now I can pass uh 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 uh 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 uh and basically what we're going to do here is we are going to uh import skeleton from components UI skeleton and we are going to import loader from Lucid react and then then let's go ahead and Export const uh 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 relative we need VG 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 going to 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. vsx I'm going to remove this and use loading from do/ comp 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 won't even able 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 uh 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. skeleton to be function infos 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 I 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 this 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 H full with full and BG muted 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 uh 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 do/ info do 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 uh participants skeleton as well so let's write participants do 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 with 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 do 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 uh take a look at how this looks now so toolbar do 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 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 these can be self closing divs so I just fix 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 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 uh self closing tag like this there we go so now we have a nice little skeleton page uh 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 uh we're going to in this 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 uh 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 live blocks. config.sys 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 so 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 uh whether the user has permission to enter that board so let's go ahead inside of convex inside of board. THS and I'm going to go all the way to the top here actually let's go all the way to the bottom and let's export const uh get to be a query from SL 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 do 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 uh 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 nextjs 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 SL 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 Al route. THS 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 Das out and then inside route. THS so this is the exact same Convention as creating uh client side pages but instead of page. DSX we have out. CS so let's go ahead now and let's import live blocks from at live blocks SL 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 back end 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 SL 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. nextt corpu convex uncore URL like that so let me just collapse this so you can see see how this is supposed to look like and of course confirm in your environment local that you have next Publix 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 nextjs route handlers so in here what we can now do is export asynchronous function post which has the request which is a type of 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 back end and front end with clerk so we can import some useful utils here like out from Clerk nextjs and current user from clerk nextjs 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 cons 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 live blocks 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 a wait convex do 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 do 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 uh image URL like this and then let's go ahead and create a session which is going to be live blocks. 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 you'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 do allow room session full access let's go ahead and destructure status and the body from await session authorized 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. 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 conso 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 conso 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 Live blocks. config.sys And instead we're going to add out endpoint and that's going to go to SL API SL live blocks Das out so first things first confirm that this/ API live blocks D out matches your new API structure app folder API live blocks 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 SL 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 spending 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 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 let me just scroll a bit down uh 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 jbt 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 back end I'm not entirely sure uh great but uh basically if in your console log right here you can see this allowed you can see this state is too high 100 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 uh 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 uh we can see still see these things is because they are hardcoded right so usually what we're going to do next is we're going to attempt to load the information of the pre presence of the users but none of that is going to work because we're 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 um if we are unauthorized but for now I think this should be uh just fine uh great so we just wrapped up and added the authorization uh 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 live blocks. config.sys of our routed THS here let me just remove all of these console logs I don't need them anymore so we confirm that this works uh oh yeah so in this user info we have name and picture so we need to add that to our typescript inside of live blocks so let's quickly go inside of live blocks. config uh. THS 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 option 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 uh 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 uh of our canvas let me inside of uh 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 used 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 yourself is it oh use self then we get me me doino 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 in for 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 uh client s side suspense loading component in 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 looks very smooth so I'm going to try it just one more time uh 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 conso log that use self hook information perfect so this is now officially working we successfully added authorization to our um authorization to our uh uh uh 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 going to 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 confirm 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 uh wrapped up the authentication uh 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 uh 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 want it to have the title of my board and I want it 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 uh and let's see so once I've added this it seems to broke my app uh 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 mpm 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 do 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 experting loading functions like this and if that is from a client component you cannot import that in a uh 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 so the alternative is that you can do things like export const info skeleton something like this let me just try it out so I can export infos skeleton separately and then in here I would import info skeleton 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 want to do that rather than this kind of export because it just feels like it's breaking my app so I'm not going to add use uh client to the Loading and I'm not going to use info. skeleton instead I'm going to export it manually now I'm going to go inside of the toolbar and do the same thing because I feel like I'm breaking some rules of react server and client components here so let's export con toolbar skeleton it's a very simple refactor so we just turn this into to 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 used client so what I want to do is I want to create an interface info props to accept board ID which is a string and then let's add that here info props 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 convas where we render the info component we no longer need this conso 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. DSX we pass that using params like that great 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 used query from convex react it's 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 uh image from next image I'm going to import Poppins from next phone to Google I'm going to import CN from lib utils I'm going to import button component from components UI button uh 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 going to pass in the source to be/ logo SVG the alt is going to be logo the height is going to be 40 and the width is going to be 40 as well and now I should have uh my logo here in the button so the same way you did in the organization sidebar right/ logo.svg and if you want for an ALT it might be better to like be more specific and say your company your app name logo and now let's go ahead and style this button by giving it a property uh 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 the last one for me is link so I'm going to add board so that 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 uh design uh 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 over write it and let's add font. class name so it takes that popins font and there we go so just make sure that you have the font constant here popins which we imported from next font Google uh and there 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 next link uh link like that and let's go ahead and wrap uh ins everything inside of the button with a link component so including both the image and the span like this and the H ref 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 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 tool tip 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 off set 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 uh like this and you can actually render it above uh 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 uh 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 F 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 popup to happen once I click on this name board right here and because we made our rename model uh programmatically controlled using tan hooks we can now easily uh 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 uh and call const on open use rename model like this and then I'm going to give this button uh an on click here it's going to call that on open and it's going to pass in dataor ID and data. 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 a 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 uh 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 comp 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 H 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 a 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 used 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 Rend under our actions component uh 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 going to have a label of main menu with a side of bottom itself and side of set of 10 and the hint is going to wrap our button component which is going to 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 we go let's try it out perfect and we now have a main menu and we can copy our link let's see if that works that works just fine perfect and we can also remove the board so now I believe if we remove the board it's just going to be in this weird empty State later we can add some redirects to it so let me just go back here and I think I need to create a new board now there we go you can see how now when you create a new board you 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 uh great so we've wrapped this up the next thing we have to do is we have to uh 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 shaten 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 Shad cnii at latest ad 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 uh 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 uh this new components UI Avatar which we've just added so that is an Avatar Avatar fullback 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 fullback and a border color so all of these things are going to be optional strings here and now let's go ahead and Export con user Avatar let's just assign this prop user Avatar props let's destructure them Source name fullback 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 uh 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 if for any reason we just can't get the name of the user the side of this uh 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 withth eight and Border two and for the Border color we're going to use the style prop because Tailwind uses just in time compiler 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 going to render the fullback whatever that is well it's a string yeah and a class name for the fullback 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 going to go ahead and import user avatar from do/ user Avatar and we're also going to import uh use others from live blocks config and use self from live blocks 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 con current user using use uh 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 it's 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 map and in here we can D structure 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 fullback 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 uh 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 uh another browser that I have on the side here and I'm going to log in as uh 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 going to add ourselves as well so in order to add ourselves we're going to 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 current user. info picture and we're going to pass in the name to be open btic current user. info name and in parenthesis 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 fullback for this also to be current user. 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 uh now that we have this 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 uh users are inside so for that we're going to use name backx users. 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 fullback is going to be plus users. 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 and it's going to allow three users to be displayed perfect so let me just try it out I'm going to go ahead uh and invite another user to this board and then we're going to see 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't 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 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 uh 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 users's connection ID to generate a unique color for them so let's go inside of our uh 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 uh with the font that's in them like this so DC 2626 D9 7706 05 9669 7 C3 a e d and db2 777 so that's my choice of colors you can add as many as you want the more you add the more unique users are going to be uh 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 uh 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. length there we go and now let's head back inside of our participants. TSX right here and we're going to do a very simple thing by by uh 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 pass in connection ID like this and let's copy this and let's add that to ourselves here and in here we're going to pass current user connection ID and for the last one uh 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 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 uh is yellow and this is going to also match their cursor when they are uh when later we add matching cursors great so you successfully added the participants here you can try it out by going 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 uh selecting the toolbox and rendering some drawings and elements on the canas 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. PSX 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 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 onclick 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 an export con 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 capital ized 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 off 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 is going to be board active which we've created a while ago so let's just revisit that to refresh our 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 do/ 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 two 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 uh tool button right here and it shows me the select uh label great and now what we are going to do is we're going to pass in the isactive to be manually false simply so we remember that we have to change this to be modular later 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 2 pencil 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 these two 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 two 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 a 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 there are going to be this disabled until we start drawing something and then when we have history we're going to be able to undo and redo um perfect so now if you refresh you can see how our loading skeleton makes much more sense because it takes the exact almost the exact height which we are going to have here perfect so that is the basic layout for our skeleton here oh sorry for our toolbar here so what we're going to do next is we're going to actually uh create an ability that when 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 ins 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 convas 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 convas State undo redo can undo and can redo like this and now let's go ahead close every everything and let's create a root folder called types and inside of it create canvas. DS 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 Anum canvas mode so let's export Anum 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. 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 uh not canvas uh 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 used 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 uh canvas State set canvas state to be used 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 enum and the canas State like this so it's going to be canvas mode uh. none like that and now let's go ahead and pass that to the toolbar so now we have the canvas state so we can pass the canvas State let's also pass set convas state to be set convas State and we have can redo I believe let's make that false can redo 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 uh 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 to 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. configs 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 use uh 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 props 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 going to use undo is disabled is going to be if we can not 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 uh disabled so let me comment out the disabled 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 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 convas State mode is convas none but also is going to be some more elements like translating when we are uh 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 transla 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. selection net like this then we are also going to have mode of canvas mode. translating we're also going to have a mode of canvas mode. inserting and we're also going to have mode of canvas mode. pencil we are going to have canvas mode. 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. vsx right here so first let's go ahead inside of text right here and see how we can make uh that be selected for example so I'm going to going to go ahead and change this textt to be is active if conas state. mode is canvas mode. inserting like this and I'm going to add an unclick here to change set canvas State and it's going to call mode to be canvas mode do inserting like this so let's try it out now 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 uh what is selected and what isn't selected right because all of multiple Fields will be able to use the convas 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 convas 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 uh elements like text rectangle uh sticky note ellipse and pen so let's go back inside of our types so inside of types canvas. vs and let's go ahead and let's first add some small reusable types which we're 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 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 and ellipse a path text and a note so I'm not going to refer to note as 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 uh 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. 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 get 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 field 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 uh so we 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 uh 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 uh and it's going to have the x coordinate y coordinate uh it's going to have the height and the width is 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 uh 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 withth and the height itself and we're also going to have an Anum side so that's going to be uh used for when we are resizing something we're going to have top we're going to have bottom we're going to have left and we're going to 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 convas mode selection net will also need to have an or 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 uh 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 uh 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. ellipse layer type. rectangle layer type. text and layer type. note like this so just make sure all 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 the pressing 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 withth and height and corner is going to be a type of side so we're going to use the XY with and hide 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 going to 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 the uh 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 the user that none of the new elements are going to appear because they're doing a specific action like translating selecting pressing resizing or nothing at all you know just uh 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 going to 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 uh let's just find there we go so if mode is canvas mode inserting then layer type needs to be one of these 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 a hexagon or something so let's go ahead and add the layer type in this case to be layer type. text and we have to import layer type from types canvas so just make sure you import layer type here layer type. text there we go no more error and we also have to be more specific with our is active prop here so let's also use canvas state. layer type is layer type. 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 conas state and is active now let's 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's going to be inserting but the layer type is going to be note we can also copy the isactive prop and paste it here and the isactive uh 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 test 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 uh a bit simpler so it's not exactly going to work on inserting the 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 uh 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 going to officially become a layer in our storage until we uh do the mouse button or mous press up event I'm not sure what what's it called only then are we going to 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 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 uh icon right here there we go perfect so if you are having trouble with this you can of course always with 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. Cs and you can visit the entire Source codes here and see whether you made any mistakes whether you should changes something uh great so now what we are going to do uh 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 at 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 uh 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 uh 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 convas dosx 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 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 uh 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 presents. 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 memorize this component and let's go ahead and Export const cursor 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 convas 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 neveress let's go back inside of the cursor's presence now and I want to go ahead and I want to import uh use others connection IDs from live blocks config like that and then I'm going to go ahead and create another component here here called cursors and that is very simply going to get the IDS from use others connection IDs and it's going to go ahead and return a fragment where we're going to go ahead and iterate our IDs do 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 going to 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 going to handle the cursor movement and now we have to create this cursor component so inside of underscore components create cursor do TSX and inside of the cursor let's go ahead and Mark 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 two 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 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 props 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 connect 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 con's name to be info question mark. name or teammate let's do that and if there is no cursor we're going to go ahead and return null and ignore this typescript error for now we're going to go ahead and extend the presence object in a second so we can resolve that great and then what we're able we're 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 live blocks config and modify this let's go inside of live blocks 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 going to go inside of our reusable component room right here and we're going to pass in the initial presence so we get rid of this error right here because we now added some 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 do/ 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 five and let's go ahead and give this a style property because all these are going to be dynamic so Phil 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 back 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 AIS 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 and add some conso 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 refresh 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 uh 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 comp component so I remembered yes that is correct actually because I never update the uh 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 convas dosx component right here and let's just collapse all of our imports from live blocks here and let's add use mutation so use mutation is a hook which can be used to kind of broadcast or stream uh some changes to the entire group of people connected inside of a live blocks room so let's go ahead and create a function const on pointer move to be used mutation and it's going to uh extract a method called set my presence and in the other argument is going to take the event which is a type of react. 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 concurrent to be pointer uh well we have to actually create a lip for this so let me add for now is going to be x0 y0 and let's just do set my presence and passing the cursor to be the current like that and then we have 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 onp 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 the 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 z 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 do 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 camerax x minus event. Delta X and Y is going to be camera doy minus E do 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 onp pointer move to be onp 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 sorry 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 is 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. client X minus camera. X and Y is going to be math. round e. client y minus camera y 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 conso log the current here make sure that you've added on pointer move to the SVG element here my apologies I can't focus the scroll and let's also add the on whe console log here so I'm going to add console log and I'm going to log X to be uh e do Delta X and Y to be e do Delta y just to convince ourselves that this is actually moving so let me go ahead and open my uh 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 we go you can see how when I scroll on 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 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 users cursor without almost any changes there we go I have my other users 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 this conso logs right we don't need them so let's go ahead and do the following we can now remove the conso log here here and we can remove the conso log here as well and now what we can do is we can go back inside of our livelocks doc [Music] config.sys 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 uh in the little box below for that we have to head back inside of our cursor component so that is located in up 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. length * 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 D5 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 uh 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 uh an the right Handler it is onp pointer leave matches onp 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 going to 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 a 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 form open back Tex 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 100 so for this tutorial I'm going to limit the layers to a 100 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 conas 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 uh layers which we have 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 last one is the text layer and of course the note layer great now let's go ahead inside of live blocks. config.log client let's add some additional Imports here so we going to need 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 uh live map of I did this twice sorry live list is one of those and let's also import layer from types conas 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 canas component so that's inside of the app board board ID conas right here and let's go ahead and let's define our layer IDs here so const layer IDs are going to be used storage which we can import from atlive blocks. 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 are 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 ellypse and depending on even more information like width height and the X and Y coordinates we're 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 use 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 be 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 uh well actually let's do it right here after we initialize this hooks here I'm going to write const insert layer to be used mutation which we already have and in here I'm going to go ahead and extract storage and set my presence and from the 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 at/ 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 going to Define what layer types can be added here so it's going to be layer type. ellipse it's going to be layer type uh rectangle it's going to be layer type uh. text and it's going to be layer type. note but it's not going to be layer type. path that's going to work on its own insert function because remember drawing is going to 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 uh sorry position which is a type of point and I think we already have point in 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 um for some reason this doesn't seem to be working Point refers to a type but it's being used this value okay how about we just continue um doing this extract this into a function and add the 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 uh an error here perfect so make sure you import point from types can 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. getet layers like this and then we're going to check if we've hit a limit on our layers so if live layers. size is higher or equal than Max layers which we Define 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 doget 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 this 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 con layer is going to be new live object from live blocks client so just make sure you add uh this import I'm going to move that uh 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 dox X Y position doy 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 do 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 that prone to errors uh 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 do push is going to get a new layer ID and live layers do set layer 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 uh we need to that selection to our live block config and let's call set State here here uh yes set canvas State mode canvas mode do 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 live blocks. config dots and in the presence here let's go ahead below the cursor and add selection to be 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 on Mouse up I believe because that's the Handler uh 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 other one wise 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 uh insert layer method here and then we have onp pointer move we have onp pointer leave and now let's add const on pointer up that's also going to be used mutation 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 coin const point to be uh 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 do mode is equal to Canvas mode do inserting we're going to call insert layer and passing canvas state. 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. none and let's see if there is something we can already pass inside of 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 uh one more thing so after this if Clause finishes let's add history. resume here and let's also add history itself in here and also insert layer because that also uses use mutation perfect and now let's revisit our insert layer here just to see if there's something uh 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 uh 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 point and our mode which is going to be canvas state. mode and let's also add layer type kind canvas State layer do we have canvas State oh we only have mode okay let's leave it like this so let's try it out I'm going to go ahead and open my terminal here I will select a rectangle and there we go when I click I have a Mode 4 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 uh 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 our on pointer up 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 individual layer ID and we're going to 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 uh 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 vsx let's import use client sorry let's mark this as use client and let's export con 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 SL layer preview like this let's go back here let's go back inside of layer preview now and let's add this 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. 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 this 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 ADD in that object and that color is going to be matching their color ID so just for now I'm going to make this an hex code of uh 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 uh so on layer pointer down there we go that seems to uh resolve the issue great and now we can just leave this like this let's go inside of layer preview props and let's import use memo sorry let's import Memo from react so we can memorize this as well so we're going to wrap this entire starting 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 con layer is use storage from our live blocks config so we're going to call use storage specifically we're going to get root and then root layers get not layer IDs layers. getet and then the ID of that layer and if we cannot find that layer in our storage we simply w't going to aren 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 uh reuse this layer preview component for all of our types for text for sticky notes for rect 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 head 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 unnown 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 uh object so let's go inside of underscore components and create rectangle. DSX if you want you can give this 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 going to 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 prop 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 the x y height width and fill I also added the value because I know is 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 on pointer down to be event on pointer down and pass in the event and ID style is going to be transform open btic Translate passing 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 bill 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 the 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 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 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 rectangle from do/ 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 ahuh on layer pointer down and selection color selection color and there we go once I did that so just make sure you imported rectangle from do/ 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 a another user can add their own rectangle 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 uh 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 uh 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 onp 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 do I call set convas State and I change the mode to inserting uh sorry this is sticky note right here rectangle I Chang 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 conas State mode is inserting so I pass in the layer type and the point where the user moved their Mouse up so where 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 f insert later okay and then what we do is we confirm that we haven't reached the maximum level level of our layers which is just a constant 100 so make sure you have this defined to 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 uh state of RGB Z representing the color black and then we push that two layer IDs and two 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 uh 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 con 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 uh 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 hardcode the selection color and we don't pass anything from the for the on layer point 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 uh selecting that element so we know if someone else is editing that element so for that we're going to get the selections of from our uh from other users presence we can use use others map from at live blocks so that Global import file that we have we can then extract the other user and we can use other. presence do 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 is going to be a record with string inside and let's get 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 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 uh and then what we are going to do is just return this object uh 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 de 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 uh map or a net of multiple selections and then we match their connection ID and create a color matching to their cursor and also uh 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 uh color prop so all we can do now is simply pass in layer IDs to color selection and passing the layer ID like this uh great so now what I want to do is I want to go uh back inside of my rectangle here so let's actually keep track of this selection color to see if we are hardcoding 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 so we're going to turn this into either a selection color or transparent like this uh naturally this is now not going to work so I have my other 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 this 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 our other user ctions but we are yet to implement the actual functionality for that uh great and here's another thing that we have hardcoded here before we can move on and that's this fill which currently is hardcoded to color black which is technically correct because inside of our canvas we do have uh where is it last used color to be RGB uh black but let's actually use that fill property which is the type of color which is basically RG GB object and let's go ahead and create a util uh which is going to be called CSS uh 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 you can write it along with me uh 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 right now this export function color to CSS uh 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 r 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 two string 16 like that pad start to zero like that then we're going to immediately add the second which is color. g to string 16 do pad star start to and zero again and then we're going to go and do the last one which is color. B two string 16 P start two and zero like that and then uh well that is it because we're immediately returning that right uh perfect so you can probably understand how it works basically we're using the radics in the two string on uh hex hexad decimal value so it's going to revert value like 255 or 255 to which would technically be ZX FF which converts to FF right you can also like do an mpm 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 transfer an object of RGB to a hex code uh 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 liues 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 uh 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 uh so just try it with 255 and uh completely black perfect so I'm going to bring this black uh 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 uh we are ready to actually create a function which we had 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 on 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 uh selection in other users and our presence so let's create on layer pointer down I'm going to do that just below our or let's do it above layer Idis color selection here so const on layer pointer down is going to be use mutation and in here let's go ahead and the structure 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 which is react. 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. pencil or if convas 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 uh 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 passing 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. 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 out 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 on the white one so now when another user selects it is very visible that they are selecting so how does this this work well let's go ahead and recap so what happens is that let's go all the way from the rectangle when something 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 canas function and then on layer pointer down ensures that this was not that this was done intentionally and not during drawing or during inserting something and then what it does is it generates our uh pointer rent to Canvas point which we use in combination with our camera right and we add that to our presence storage and in the form of selection array passing that new layer ID here and then we also change the convas state to translating so that we can you're going to see that in a second so when 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 implemented 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 elements and not just the other person like this so let's go ahead back inside of our convas dosx right here let me just close everything but the canvas and what we're going to do is we're going to go inside of our SVG element right below the layer iteration and in here we're going to add a new component called selection box it's going to be a self-closing tag and it's 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 and import Memo from react like this also I don't know if you notice but you don't have to always Define use client so if the child element If the child element's parent is used client that will automatically turn this into a client component as well I don't know if you knew that uh except if you are passing uh it through CH children so if you did for example children here then children could be a server component but if um if you don't mark this as used 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 used 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 con selection box be a memo method uh my apologies that's not how you write a memo function function like this selection box display name selection box and let's go ahead and this structure 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 do/ sele 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 resized handle pointer down so the props is going to accept is the corner which will have a type of side from our types conas and initial initial bounds which will be a type of x y w and H like that great uh 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 Soul layer ID is going to be use self from live blocks config let's get me and then this will just import yourself 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 null and now let's go ahead and write const is showing handles so we are 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 live blocks config so we're going to get the root like this and let's go ahead and write if Soul layer ID if we have soulle layer ID and if root layers get so 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. 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 uh an element so right now it's easy we know that we just have to use the height and some points around this boxes but how about later when we allow a selection net how will we know how big that selection box box needs to be well for that we're going to need a reusable hook called use selection bounds 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 use selection bounce let's go ahead and import shallow from live blocks client uh actually let's import it from live blocks react like this and now let's import layer and let's import X Y uh 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 uh y with and height or nothing or if we cannot find the 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 Bings so let's write four let I is equal 1 I is less than layers do length and i++ 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 uh X right and I forgot that we have to assign this 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. y plus first do height so all the way to the bottom and now we're going to go ahead and check whether TR 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 com 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 + 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 con use selection bounds and let's write con selection to be use self let me just write con selection use self let's get ourselves and write me presents 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 do 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 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 live block react to our use storage so make sure you have this shallow here great so that is our use selection bounds ready to be used so we can go back inside of our selection box component component inside of underscore components in the board ID here selection box and then we can do con bounds to be use selection bounds like this so import use selection bounds from hooks use selection bounds and then if there are no bounds we are going to return null 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 fil transparent stroke blue 500 stroke one and pointer events none and let's pass in the style to be transform open backx Translate and now we're going to use the bounds x value in pixels and Bounds one 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 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 RCT 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 Phill 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 NW SE resize width is going to be dynamic so let's go ahead and add handle withth 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 Tex and write translate and we're going to use bounds. 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 bouncey minus handle width / by two again in pixels like this so let me go ahead and try and there we go you can see now when I click on something I have a new indicator in a little box here that I can try and resize this you can see how it shows me that I I'm going to be a to do that in this or this direction right here great so now we have to do that uh 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 do stop propagation like this and then in here I'm going to add to do add recite 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 easier for us to do it so we're going to write translate the first one is going to be bounce. X Plus bounce do width / by 2 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 with divided two pixels so don't forget this pixels here and there we go you can see how now this one is in the middle of my uh in my on 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 uh 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 going to have uh a different cursor of course this one is going to be uh NW 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 going to do bounds oops bounds. x minus handle width / 2 plus bounds. width in pixels comma bounds. y coordinate minus handle width / 2 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 bounce. x minus handle width / by 2 plus bounce. width pixel and the second argument is going to be bounce. y plus bounce. height ided by 2 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 going to create the one in this corner here so that's going to be n w s e resize n WS resize like that let's clear up our translate as always so let's write uh the first one is bounce. x minus handle width ided by two plus bounce. width pixels the second argument is going to be bouncey minus bouncey minus handle width / by two uh plus bounds height pixel 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 going to 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 from for just repeating this you can just go into my GitHub and copy this 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 uh bounds X Plus bounds width ided by two minus handle width ided two uh in pixels and then the other one is going to be bounc at y minus handle width divided by two plus bounds 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 uh 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 uh well almost last this one is going to be NW resize and let's clear up the trans here so this one is going to have the first one of bounds. x minus handle withth divided by two pixels and the other one bounds y minus handle width 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 bounce x minus handle width ided by two pixels and the second one is going to be bounds y minus handle width divided by 2 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 trend 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 just if you don't care if you just want to type it out that's okay uh but you know just in case you want to specifically learn how it works you can do this it just makes uh it's easier for the eyes to look at it in this way okay 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 uh okay so we have this and let me just collapse this as well okay so just make sure all of your translates uh are correct you can always visit directly in my GitHub and copy the individual transform methods here if you're having any problems uh 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 uh 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 reiz handle pointer down uh 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 point down to be empty for now so we're not going to immediately add this instead let's just create this on reiz handle pointer down in the canvas component so inside of canvas. vsx right here I'm going to go uh 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 the doesn't start to drag yet just clicks on 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 going to 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 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 oniz uh handle pointer down and let's actually pass it to our selection box here so on reiz handle pointer down we'll be calling the oniz handle pointer down function right here and now let's go inside of the selection box and let's actually use that uh function now so inside of the on pointer down here in the first handle let me just find it we have a to-do address siiz Handler let's go ahead and write on resize Handler pointer down we're going to call side. Top plus side. 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 uh 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 uh 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. 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 going to go to side right like this that's this one and then again we have a bottom corner here so we're going to 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 inside 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 uh 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. 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 wct is our side do 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 side 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 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 on resized handle pointer down you should be getting uh some conso logs here so let's go ahead and conso loog 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 9 Corner 8 Corner 10 Corner 2 Corner 6 Corner four corner five like that so all of them are different perfect and we also have the initial bounds of their width height and there are 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 uh let's go ahead and find on pointer up or on pointer move I'm not sure which one would be uh 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 canvas state. mode to be canvas state do resizing we have to use a type canvas State my apologies canvas mode. resizing if that is true let's just conso log resizing so now if we are correct so let's find our own resize handle pointer down what it does it it changes the convas state to convas mode resizing and it passes the initial bounds in the corner we are trying to resize so then inside of our on pointer move 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 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 uh so we need conver State inside of the dependency array let's see if that will maybe help it there we go so I was missing uh the dependency right there we go you can see how if I hold and 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 uh resize selected layer so that we can actually well resize it so I want to add this in here where we have our uh 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 used mutation 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 cell 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 canvas state. mode is not canvas mode resizing so if we somehow fired this function and 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 XY 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 XY withd 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 dox y bounds doy width bounds. width height bounds. 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 typescript 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 in 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 dox is going to be math.min so the smaller value of this is point dox or bounce dox plus bounce. width and result. width is going to be math. absolute bounds. X Plus bounds. width minus point dox 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 dox now let's go ahead and write if corner and side top is equal to side top in that case result. Y is going to be map Min between point Y bounds doy plus bounce. height and result. height is going to be math absolute bounce doy plus bounce. height minus Point doy and last thing we have to do is the bottom so if corner and side bottom is equal to side bottom like this result doy is math Min between point Y and Bounds Y and result. height is going to be math absolute point Y and bound minus bounce. y coordinate like this so let's just confirm we have the necessary cases so we have the side bottom case 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 conas and now what we are going to do is we're going to actually uh get this function and get the bounds so let's write const bounds to be uh resize bounds from lib utils so just make sure you import precise bounds from uh lib utils the same place we have the connection ID caller and pointer event to Canvas point and in here we're going to pass the canvas state. 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 con live layers to be storage. getet layers and then con layer individually is going to be live layers. getet self. presence do selection first in the array if we have that layer we're going to do layer. 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 actually not just yet so 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 uh where is it uh 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 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 uh 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 size 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 uh and see exactly where I did wrong yeah especially when I grab the complete opposite side this is not exactly how I would expect it to behave but yeah we we are doing something that's good uh so now I'm just going to go ahead uh throw out individually looking at looking through my um well this method right called reiz Bounce and I'm also going to 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 uh so you don't mess up your code and uh 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 Reise bounds right here which is inside of our utilus 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 onp pointer move if we are translating so let's find our onp pointer move method it should be somewhere up here there we go so we have the case uh we have the case for resizing and now we have to add 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 like this it's not firing that event only if I move it strictly on this layer great now we have to create a method uh 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 a selection net it's going to be used mutation as always and let's go ahead and prepare this like that and now I want to D structure 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 uh any complicated calculation for this one so what we have to do is add an x coordinate to be Point dox minus canvas state. current dox and then we're going to have y to be pointy minus canvas state. current doy and then let's get all of our live layers with storage get layers and now let's go ahead and do for con ID of self. presence do selection const individual layer which we are selecting in the present is going to be Live layers. getet ID and if we can get that layer we are going to update its position using the x coordinate layer. getet X Plus offset dox and y layer. getet y plus offset doy like that and then outside of this four const we are simply going to do set State set canvas State mode canvas mode. 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 uh translate selected layers down here in our pointer move where is it on pointer move so in here we are simply going to call translate selected layers and pass in 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 all 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 convas and let's add onp pointer down method so I'm going to go ahead and do this uh right above onp pointer up so let's do const onp pointer down to be used 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. 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 and the camera and then we're going to check if convas state. mode is canvas mode inserting 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 commment here to do add case for drawing and for now what we're going to do is just use set convas State 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. mode set State and start drawing actually start drawing is what we're going to have later so we don't have that yet and we need set cona State here like that great so we have onp pointer down let's pend onp pointer down right here in the SVG element onp 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 uh handle the inserting case here so let's go ahead and modify this a bit by by checking if canvas state. mode is in canvas mode. none or if whoops uh 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 canas state to be mode canvas mode do none again so this will kind of unselect our layers and then we're going to 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 uh 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 unselect event to deselect all of our layers so let's create a method called unselect layers and we can add that somewhere where we work with layers on Wheel on resize Handler pointer H okay here it is resize selected layer so let's add const unselect layers here to be use 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 uh 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 onp pointer up method which we just added this conso 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 change the layer depth right so for example if I want to hide that uh 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 this 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 undos should be working so both selecting and redoing all of that should be working uh 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 component 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 uh our new component which is going to be called selection tools and now let's go ahead and passing 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's go inside of the underscore components folder and let's create our selection tools. TSX let's mark this as use client and let's 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 oops which we also import from types canvas and it simply returns a void and then we can export con selection tools and very simply let's also memorize 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 D structure 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 do/ selection tools the same way we did with the code this presentence for example now let's focus on being inside of this selection tools right here uh let's go ahead and decide whether we should render this or not so con selection is going to come from use self which we can import from live blocks. 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 uh bounding box uh 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 sele ction 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. width ided 2 plus selection bounds. X Plus camera dox and y coordinate is simply going to be selection bounds do y plus camera doy like this and now let's go ahead and give this div a class name of absolute adding 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 btic Translate and in here let's go ahead and let's pass the first argument to be uh calculate go ahead and write X in pixels minus 50% like this and then the second is going to be calculate again y value Min - 16 in pixels minus 100% like that and let's see if we can already see this so so if I select something there we go you can see how it appears nicely above and follows my uh 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 going to 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 here and create the Color Picker let's mark this is 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 aoid and let's export con 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 from do/ Color Picker like this and now when I click here it should say a Color Picker so we can continue working in the Color Picker component now uh so let's go ahead and quickly create a uh 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 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 items Center Max width of 164 pixels adding right of two margin right of two 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 uh 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 it's just going to be an empty well we can pass in the actual onchange right we have it in the props onchange and the color can be whatever we Define in RGB values so for example R can be to 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 uh width eight height e 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 a 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 liby 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 it can 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 uh all of these 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 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're 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 onchange of the Color Picker so inside of the selection tools here let's go ahead and let's create a method called Uh set fill so const set pill is going to be use mutation from live blocks uh config right here and let me just go ahead and return this this and we are going to extract from use mutation here storage and we are also going to extract pill which is a type of color which you can import uh from types canvas and now let's go ahead and do con 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're going to do for the selection which we loaded right here above for the layer we are selecting for each ID live layers doget ID question mark in case it doesn't exist do set the fill value is going to be 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 uh from live blocks let's go ahead and use set fill here now and let's simply uh pass it here for the un 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 uh layers great so how and why does this work well you have to ensure that inside of your app folder uh board ID components in the rectangle here you are properly using the fill extraction from the layer so make sure that you didn't let leave this as hardcoded 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 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. DS let's import use self from live blocks config and use mutation from live blocks config as well and Export con use delete layers first let's get the selection so we know which layers to delete so we get me me. presence do selection and then let's simply return use mutation here let's prepare it like that and in here we're going to go ahead and extract uh the storage and set my presence from the first argument my presence like that so let's get live layers to be storage doget layers and con live layer IDs to be storage doget layer IDs and now we're going to do four const ID of selection which we have live layers. delete ID and then const index live layers dot sorry live layer IDs do 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 do 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 add added the set fill for the Color Picker and now what we can do is we can import that hook so con delete layers use delete layers from hooks use delete layers make sure you added the import uh 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 component UI button and we also need to import hint from components hint and I would also like to import trash 2 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 Flex item Center padding left of two margin left of two border left and Border neutral 200 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 two 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 going to be two more buttons for 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 I it's inside of the Ed delete layers uh my dependency array is empty but I rely on the selection from here so let's go ahead and ensure that we passing the selection inside of my use mutation right here so this selection gets used this is inside of used delete layers and nothing else is changed let's try out now and there we go now exactly what I click gets deleted perfect so we have the 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 sele 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 the two buttons which we're going to have let's give their wer 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 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 it 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 uh 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. getet layer IDs and then let's create the Matrix of indic indices the 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 SEL 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 z i is less than indices do length so of the maximum of that array and let's increase uh our index at the end live layer IDs do move indices I and I here like that and make sure to add the selection in the dependency array of the use 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 uh 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 if 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 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 indis say. 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 IUS minus and then let's do live layer IDs do remove sorry move in the first argument indices and the index and in the second one array. length -1 minus in parentheses indices do length -1 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 the icon bring to front so let's go ahead if 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 uh which we need here uh 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 to 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 conas 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're 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. mode is canvas mode do 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 uh 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 M multi selection to be use callback not use mutation so just use callback and inside of here let's repair this with an empty dependency array we are going to accept Uh current to be a type of of point which we have from canvas types from types canvas and origin to be Point as well so we we're going to know where it started and where it ends and now let's go ahead and write if math. absolute c.x minus origin dox plus math do whoops so this is uh yes is still inside of the if Clause so another math absolute which is going to say if current do y minus origin doy let me go ahead and collapse this so it's maybe easier for all of us to understand so if math absolute current dox minus originx plus math absolute currenty coordinate minus originy coordinate is less is sorry High uh higher than five inex that case we're going to go ahead and write set State set canvas State mode canvas mode. 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 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 use 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 uh like a magic it seems like it's a magic number usually there is a practice when you're working companies this is called a 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 uh but you would probably store this in a constant for example uh 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 uh create any more complex code because this is complex enough already so just confirm that this is working for you and now what I we what I want to do is go back inside of the onp pointer move method and now we're going to go ahead and add another else if here before the uh translate selected layers here so let's go ahead and inject our l if here again let's open it and let's just prepare it like this so else if canvas state. mode is equal to Canvas mode. 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 convas state origin so we don't have the update selection n 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 broadcast broadcasted to everyone uh 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 write get our layers using storage. getet layers. two immutable 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 short hand if you want to and now we have to create a util called find inter intersecting layers with a rectangle so we're going to use that so that we Define uh 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 utilus folder in GitHub and copy the finished method so expert function find intersecting uh find intersecting layers with rectangle the first argument is going to be layer IDs which is going to be a readon string the second argument is going to be layers which is going to be readon 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 uh 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 uh Y coordinates so ay and b.y and the width is going to be the absolute result of a.x minus B dox so the two points from one another and the height is going to be the same thing but the uh y coordinate so a.y minus b.y basically the difference between the two points on all axis of the coordinates and now let's create an array of IDs here and let's iterate over them so for const layer ID of layer IDs we're going to go ahead and write const layer to be layers. getet layer ID if we cannot find that layer and let's use null specifically like this let's write continue and 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+ width and if rectangle do y plus rectangle. height is higher than the y coordinate uh not react sorry rect rect height like that and let's do if rectangle doy again is smaller than y + height ID is. 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 Libs and now in here we're going to pass in the lay 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 didn'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 uh 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 uh 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 canvas state mode is canvas mode selection net and if canvas state. 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 Phil blue 500/5 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.min canvas state. origin. X and canvas state. current. X and let me just see if I made any mistakes here canvas state. current is possibly undefined so let me just confirm that shouldn't be an issue because of this canvas state. current oh we can use the 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. origin dox minus canvas state. current dox 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. originy minus canvas state. current doy 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 WRA 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 layer pointer down my apologies and now let's go ahead and create this one so ellipse do PSX 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 Li utils 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 con ellipse let's assign ellipse props and let's go ahead and D structure 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 backx and let's write translate and let's go ahead and pass layer dox in pixels as the first argument and layer. Y in pixels as the second argument and let's pass in CX to be layer with divided by 2 Cy to be layer height divided by two now let's pass in RX to be layer width divided by two and let's go ahead and add r y to be layer do height ided 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 do/ ellipse the same way with 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 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 uh 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 mpm install react content editable like this and now let's go ahead and let's 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 I want 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 Libs and color to CSS from Libs and let's import text layer here from types canvas and let's import use mutation from live blocks. config and now let's define our font here so colum or Calum colum 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 uh 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 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 uh actually I think we need to use color to CSS here uh 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 uh 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 do/ text the component I just created and let's go ahead and create a case layer type. 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 uh 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 PL items Center justify Center text Center who text Center drop shadow MD outline none and let's add Dynamic font. class name there we go so now you can see how this font now looks like uh 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 fill 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 uh 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 uh 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 uh weight whatever you want right and then you would simply uh 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 con 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 Fone 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 con font size based on height is going to be height times scale factor and let's do con 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 m 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 to it's going to have more sense when we actually had a feature to uh well synchronize the text so how about we uh Implement that instead first so let's go ahead and let's create a method called update value const update value is going to be used mutation from do we already have use mutation we do use mutation from live blocks config all right so use mutation 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. getet layers and then live layers. getet using the ID of the layer which we have question mark in case we don't find it do set the value field is going to be new value and we don't have to add uh 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 uh the value which we've extracted from the uh 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'm going 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 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're 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 Excel and inside of the content editable here let's go ahead and modify uh 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 uh on the entire for in object and now what we are going to do here is 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 Val threshold values and it just Returns the opposite color I guess so luminance is going to be 0.299 time color. R plus 0.587 * color. G Plus 0 114 * 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 slote 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 it 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 uh 0.15 so this is going to make it uh 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 write 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 uh black right so that contrasting thing obviously works I'm not sure how but Chad GPT 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 compon component 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 cons the 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 conso 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. 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 these pointer events like reiz 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 canvas and pressure which is going to be a number like that now now let's go ahead inside of here and let's do uh set my presence pencil draft is going to be an array and then inside of it another array point dox point doy 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 uh our live blocks 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 live blocks. config.sys 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 Pen 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. DSX 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 the next one is onp pointer move so let me just find where do I have onp pointer move there we go onp pointer move right here so we have the stat for pressing selection net translating and resizing so after resizing last one let's add else if canvas State mode sorry mode is identical to Canvas mode. pencil in that case we're going to go ahead then 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 uh above my continue uh start drawing so con 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 let's Destro structure from here self and set my presence and now let's get the point to be type of point and event to be 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 canvas mode. pencil or if event. buttons is not equal to one or if pencil draft is null but I'm using uh 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 set my presence and let's pass in the cursor to be Point pencil draft to be pencil draft. length is equal to one and pencil draft the first first array from the first array is equal to point dox and pencil draft from the first array the second element is is point doy if that is true we're going to add a Turner 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 dox point doy and e. pressure there we go and make sure to pass in the canvas state. 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 on pointer up so let's go ahead and find our on pointer up 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. 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 convas 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 con 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. getet 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. length is smaller than two live layers. 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 pinpoints 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 penp points to path later 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 number of uh 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 be number negative Infinity as well and now let's iterate over our points for 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 layer type. path uh let me just see if I'm having any M 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 uh y coordinate 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 D structure 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 uh 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 uh examples from live blocks and you know just a 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 uh it's the best I can do for now uh now let's go ahead and continue doing this by adding live layers do set and pass in the ID and new live object inside is going to be pen points to Pat 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 con live layer IDs is going to be storage. getet layer IDs and let's go ahead and write live layer id. 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 V 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 going to prepare my terminal here at least I think that's what what will happen if I try and draw something once I release I'm 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 uh 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. 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 do 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. TSX component right here so I'm first going to install a package called perfect freehand so mpm 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 create 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 react. 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 D structure 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 do/ 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 going to call the event and call on layer pointer uh actually do I need to do this like that let's do it like this let's pass in the id 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 Str 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 events and passing 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 the 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 doy and we are missing the field value to be layer do fill and then we're going to use color to CSS and pass in layer. fill or it's going to be 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 uh using this get stroke freehand here and that fun 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 not that one sorry this one get SVG path from stroke so I wish I could have 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 uh so you can find this function get SVG path from stroke either by pausing 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 pad from stroke great so that will speed things up again it's not a Long Function it's just just so confusing that honestly uh you're going to see where we use it so we use it inside of our app folder board components uh where is it our path right here so in here we're going to use it in combination with our G 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 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 ones work great for me 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 uh 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 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 uh 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 uh 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 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 uh 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 live blocks config 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're going to go if if pencil draft is not null but using equal sign not identical and if pencil draft. length is larger than zero in that case we're going to start rendering our path component from do/ path so the same path which we are reusing uh where is it which we are reusing for our layer preview component so that's the same component which we just created and then we're just going to have to pass all the necessary so points are going to be pencil draft and our fill is going to be color to CSS from lib SL 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 Z 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'm selected something above and accidentally removed it so fill is going to be get sorry color to CSS from lib utils and last use the 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 uh 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 uh real time for us but here's another user and another user will 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 them to for us to see they're drawing in real time and for that we're going to go back inside of our cursor's presence and we're going to 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 con drafts so drafts is going to use others using use others maap which you can import from live blocks. 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. presence. pencil draft and Pen color is going to be other. presence. pen color and we're going to go ahead and use shallow here which we can import uh from live blocks client let me just see that there we go and let me just collapse these two 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 D 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 SL paath so let me just show you this/ 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. pencil draft pill to be other. 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. 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 going to 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 uh improve your project so I want to show you uh 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 uh 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 uh to prevent that so let's go inside of hooks and we're going to go ahead and create use disable scroll down so use disable scroll bounce. DS 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 bounce and we're going to call use effect and very simply inside on the mount we're going to call document.body do class list. add overflow hidden and overscroll Das none and let's go ahead and return document body class list remove overflow hidden and overscroll none so that happens on unmount great and now let's go and simply include this inside of our canvas. CSX 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 contrl Z or backspace to delete something we can do that as well so 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 I actually do that just before our render function to ensure we have all of those functions from above yeah uh let me move it okay here is the return function so I'm going to 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 do use delete layers from hooks use delete layers there we go so I'm going to 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 uh function on key down event is going to be a type of keyboard event and in here you can do a switch on the E do key and for example example if Case is Zed you can go ahead uh 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 for the other cases you can simply break uh sorry let's add break right here like that and let's go ahead now and simply add document add event listener and pass in 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 pass in 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 a lot of changes let's 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 be uh 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 are 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 uh great but what I want want to show you next is just that there are a couple of stuff which I've been told from the live blocks 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 do replace those with uh two immutable like this so let's find so the first one was uh I'm not sure where where this last one was H too 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 uh the is the two array is deprecated so let me check if two array exists anywhere 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 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 two array so it could be just the version live kit 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 what 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 going to 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 uh line so that's why it was not it was not visible great so if you have any instance of converting to array you 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 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 mpm run build this must not have any errors for us to be able to deploy so we can save some time instead of uh wasting time on ver cell and as you can see at least on my version of nextjs I am getting an error export 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. vsx let's wrap our entire convex client provider in a suspense from react so let me do this like 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 full back here to be our loading from components out loading 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 mpm 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 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 get add and then do get 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 next1 14 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 this second option for an existing repository right here and paste it and run the commands after this has been uh pushed you can reset refresh this page right here and now you should see uh 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 uh from convex to deploy on versell so let's go ahead and deploy something on ver.com slne 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 -- 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 environment. loal 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 so let's go ahead inside of our uh 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 unor deploy 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 uh from development to production right so you have two instances here so you can change to production and change the API Keys here as well generate a new secret key let me try and generate it uh for some reason I cannot copy it also now it's appearing okay no 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 uh 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 uh 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 addition content where I turn this into a software as a service by introducing a stripe see you in the next video
Info
Channel: Code With Antonio
Views: 251,814
Rating: undefined out of 5
Keywords:
Id: ADJKbuayubE
Channel Id: undefined
Length: 619min 11sec (37151 seconds)
Published: Mon Jan 29 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.