{
  "video_id": "dllIBXa0nlE",
  "title": "Build and Deploy a Multi Vendor E Commerce With Nextjs, React & Stripe Connect (Part 2/2)",
  "url": "https://www.youtube.com/watch?v=dllIBXa0nlE",
  "transcript": "Hey there, my name is Antonio and in this tutorial, we are building a multi-tenant e-commerce application. Right now, we are looking at the public storefront. Each product here is sold by a different merchant. Everything's categorized cleanly and we have a slick drop-down menu for subcategories. When a customer clicks on a product, they get taken to a dedicated product page. But notice this, the URL has changed. We are now on a vendor-specific subdomain. That's because each seller on our platform gets their own branded storefront. Once the user adds a product to the cart, they are taken to the checkout screen. From here, they can either complete the purchase, or they can go back to the merchant's storefront and add more products from the same vendor. This is the vendor's public storefront. It has a similar layout to the main homepage, but it's filtered down to only this merchant's products. The store is branded with the vendor name and completely isolated. Again, we are on their unique subdomain. After clicking checkout, we are redirected to Stripe checkout screen. And what's really happening behind the scenes is powered by Stripe Connect. The vendor gets paid, and our platform automatically takes a 10% fee from the transaction. That's our revenue model, and you will learn how to build all of this. After a successful purchase, the product shows up in the library. This is where customers can access their purchases and leave reviews if they want to. When clicking into a product, customers can leave a rating or a written review. And they can also access bonus content uploaded by the creator. The content block supports rich text, so it could be a file download, a welcome message, a license code, a link, anything the seller wants to include. Here we have the merchant dashboard, powered by Payload. This is where vendors manage their products, set prices, update descriptions, and assign categories. Before creating anything, they'll have to go through Stripe verification, which is something we handle directly in the dashboard. And finally, here's this super admin dashboard. Same clean structure, but with access to all platform entities, users, products, orders, tenants, and more. This is where we demonstrate role-based access control. So vendors only manage their own data, while the admin stays in full control of the platform. So, if you are ready to build a fully featured multi-tenant marketplace with payments, Stripe Connect, reviews, role-based access control, and more, let's get started. In this chapter, our goal is to set up Next.js. In order to do that, we first have to confirm our environment. So it requires a specific Node.js version or all versions above that. So let's quickly go inside of our terminal and let's run node-version, which is going to print out the current node version. In case you've gotten an error here, or you've gotten a lower version than 18.18, it's time for you to get the latest long-term support, which you can find on Node.js.org. The second thing we have to handle is our desired package manager. so it's not gonna matter too much which one you use but i just want to give you a tip that i will be using bun and i would highly suggest you use bun as well this is of course a personal preference but here are some reasons why i find bun uh more interesting or better over all the others which are npm pnpm and yarn first of all when it comes to bun install versus all the other equivalents it is just unbeatable in speed and that also accounts for the npx equivalent which is in bun's case bunx so once you try that all of your installs are going to be so fast that going back to npm pnpm or yarn will just sound funny a second close is pnpm it is absolutely amazing but I've tried both and Bon was faster at least for me. The second reason is zero config script running so we are going to have some scripts which we are going to write in TypeScript and with ES6 imports so it's important for us to have a way to run these scripts independently and it would help us if we have out-of-the-box support for the type of code which we are going to write. As of some newer versions, Node.js officially supports TypeScript out of the box, but I'm pretty sure it still doesn't support ES6 imports out of the box. And lastly, it's better at resolving problematic packages. So since React 19 came out, a lot of packages did not upgrade their peer dependencies. And then when you try to install that package, npm actually throws an error, whereas bun throws a warning or at least that was the case uh in my previous video right that was why i chose bun because it was very problematic uh because npm was problematic uh so i'm going to be using bun again perhaps this has changed i've seen some comments people telling me hey i got an error as well so we are going to see but this was the last experience that i had with it so how do you install bun you can visit bun.sh so what is bun here's the official definition bun is an all-in-one javascript runtime and toolkit designed for speed complete with a bundler test runner and node.js compatible package manager it also aims for a hundred percent node.js compatibility so when it comes to uh bun's speed as a runtime you can also see that it's quite amazing but I'm not going to be focusing on that. I already told you my two, three main reasons why I'm using it. This one, it's incredibly fast when it comes to installing packages. And the second one is that it truly is a all-in-one JavaScript runtime and toolkit. It can just run things with zero content. I absolutely love that. And you can, of course, you know, try scrolling down here. and you will see built-in core features, everything that Bun has in comparison to Node and Dino. And I think it's just super, super impressive. I found it to be everything I need when it comes to building projects. So I will be using Bun and I suggest you do as well. You can install it both on Unix systems and on Windows with one line. And after you've installed it, you can try it out by running Bun-version. you will also then have access to Bunix like this. And just in case you were interested, in case you've never seen package managers or you come from some other environment, when you install Node, you will automatically get NPM. So you can confirm that as well. And you will also automatically get MPX. So the reason I'm showing you this part is very simple. For example, just in a moment, we're going to go ahead and run Next.js installation. And you can see that they clearly outline npx here. But I'm not going to be writing npx. Instead, I'm going to be running bunx. So in case that confuses you, all I'm trying to say is it's the same thing, right? So bunx is the bun equivalent of npx. I just find it to be much, much faster. So I just want to get that out of the way so it doesn't confuse anyone. All right, so I think we're pretty good when it comes to explaining this initial part. So we've confirmed our environment and I've explained my choice behind using Bun over all the other packages here. And now it's time for us to set up Next.js and then verify that you have the exact same installation that I do. So let's go ahead and do that. If you want to, you can visit the Next.js documentation and go into the installation here, or you can just follow exactly what I'm doing. So one important thing in this tutorial, I'm gonna be using specific versions, right? So I will not just use at latest. Instead, I'm going to check what is the latest version and then I'm going to use that version specifically. So I'm going to copy this just so you can see how this would look like if you're just using npx and npm, right? So I'm going to modify this to Bunix because that's what I'm using. And instead of just running this app, I'm going to add dash dash version here. So this might actually prompt you to install something. If it does, you can just say yes, because it needs to install the latest version. So at the moment, the latest version of Create Next app is 15.2.4. So what I'm going to do is I'm going to run Bunix Create Next app at 15.2.4. that's the way i'm going to be doing it and now we have officially started the next js setup wizard so i'm going to name my app you can call it whatever you want for example multi-tenant e-commerce when prompted whether we would like to use typescript select yes same for eslint and the Same for Tailwind CSS. I would also highly suggest that you use a source directory as well. So by default, it might be set to known. So you can use arrow keys to select yes. The app router is absolutely crucial. Make sure that is selected for yes. TurboPack, as I've understood, it's actually optional. It shouldn't change anything for you. Apparently, it's much faster if you use it. But since I originally did not develop with it, I'm going to select no here just because I want to get the exact same results. And we're not going to modify the import alias. So we are going to leave it to be the at sign. And now we can just wait a couple of seconds until, in my case, bun installs all the packages I need right here And there we go So 12 seconds for 312 packages I'm not sure if other package managers can do that. This might be a bad example. Maybe they can. I honestly don't know what's the standard, but this felt pretty fast for me. So now that we have this installed, it's very important that you change directory inside of your newly installed project, right? You don't want to be running any commands before you do that. And now you also want to open that new project inside of your IDE. In my case, that is going to be VS Code. You can use anything other, whatever you want, basically. So I'm going to go ahead and click open here. and I'm going to go ahead inside of my workspace. And there we go, multi-tenant e-commerce. So if you get this prompt, you can press this button here. And now I think it's important for us to just go together throughout the structure just to ensure that we have the exact same things here. Let's start with the package JSON. This is the crucial one, right? People often ask me to show this in the beginning and I absolutely agree. I should be showing that in the beginning. So here are my dependencies. I'm using React 19, React DOM 19, and Next with 15.2.4. And here's a very, very important thing. I'm using Tailwind version 4. So the last version where Next.js uses Tailwind 3 is, I believe, 15.1.7. I think that's the last version where you can find Tailwind CSS version 3 installed. And honestly, I was thinking and contemplating long and hard, which version should they use for this tutorial? Because originally I developed this with Tailwind version 3. But I figured by the time everyone watches this video, by the time other people find this video, Tailwind version 3, version 4 will certainly become the standard. So that's why I ultimately decided, okay, we're just going to use the latest Next.js version and the latest Tailwind version. So that is it for package.json. Now let's go ahead and let's check out some other, you don't have to go inside of these files, but just confirm that you have the tsconfig, confirm that you have post.css. we already went over package json confirm that you have the next config the slint in my case i have bond.lock which is the lock file for my bond package manager if you opted for something else you might have a package json.lock yarn.lock or the pnbm equivalent and inside of here you should also have the source folder which should hold your app folder and finally inside of the app folder you should find all of these things right here. So you should also have the public folder and node modules outside of the source folder. So after you have confirmed that this is your structure, you are good to go. So the most important thing here, the Next.js version, 15.2.4, the React version and the Tailwind version. Because if some of these things don't match, you might run into some breaking changes, which I'm not aware of, right? Especially if you're watching this far into the future, I might not be aware that something has changed. So that's why I'm suggesting that you use the same version that I am. So let's finally go ahead now, ensure that you are inside of your project here. And you can just do bon run dev or npm run dev or yarn run dev or pnpm run dev, whatever your package manager is. And now you can visit localhost 3000 and there we go. You can now see that we have our app right here. So now that we have our app, I believe that our next step here was to install ShadCN UI. So what even is ShadCN UI? So ShadCN UI is not a component library. It's how you build a component library and I think that's an absolutely amazing description because that's exactly what it is. It's basically a set of beautifully designed, accessible components and a code distribution platforms. So what we're going to do is we're going to run through the installation process here. As you can see, they have some separate instructions for Next.js 15 and React 19 and Tailwind version 4. So again, here I was contemplating what should I use here? Should I teach you Tailwind version 4 or not? but I think ultimately this will be a good decision for us to use Tailwind version 4 simply because from now on that's going to be a standard. And if you go inside of installation and choose Next.js you will see that they have, at least I have a note the following guide is for Tailwind version 4. If you're using Tailwind version 3 you have to use this version of ShatZN. So in our case we are going to go ahead and check what is the current latest version and then we're going to run this command using that specific version simply so you can see what version i am on and as you can see here we have a nice little selection for our package managers in my case that's going to be bun so ensure that you are inside of your project here and i'm just going to go ahead and change this to not be in it but instead just to be a version here so let's see what version am i using there we go so 2.4.0 dash canary dot 17 so at the time of making this video the latest chat scene version is in canary which basically is canary is often used i think by definition uh to you know first show a feature to a smaller group of people before you fully release it. But since they have put it publicly on their website as the instruction, I'm pretty sure this is more than stable enough to be used. So in the future, you can visit their GitHub and you can check what is the latest version. So in case you check this in the future and it says 2.4.0 without this, that's perfectly fine. That just means it's now fully out. In my case it's not yet fully out so i have to use the canary version so i'm gonna go ahead and set this replace the latest with 2.4.0 dash canary 0.17 like this and i believe uh i don't have to add anything after that oh i do have to add in it so let's just add in it there we go so bun bun x dash dash bun shat cn at 2.4.0 dash canary 0.17 in it and there we go so we are now uh you can see it detected next js and it also found a version for tailwind so this is very important that you get these two checks here for your base color you can use whatever you want i'm going to be using neutral and there we go so now you should see some new files here so for example inside of my source folder i now have a lib folder which has my utils and inside of here i have a cn short for class names util this will be very useful for when we have to conditionally merge some tailwind classes because even though you might think that it's super simple it is it is super simple but if you are not careful, you might accidentally override some classes. And this util will prevent you from doing that. And it's just a handy util to have. Besides that, you can click here if you're using VS Code. So you can see exactly what has changed. So you now have a components.json, which is basically a schema file for all of these options, which we selected right here. You can see the New york style base color is neutral and the aliases that we've set here right in the package json you should see a couple of new classes a couple of new dependencies installed so that is class variance authority clsx lucid react which we're going to use for our icons and tailwind merge and Tailwind animate CSS. The next was not modified. It was just moved from up there to down there. So don't worry about that. And you can see that globals.css has also changed and might I add quite drastically changed. So let's go ahead and run this app and see if everything is working fine so i'm gonna go ahead and do bon run dev localhost 3000 here and there we go so what i immediately see now is that even though my uh system is in dark mode uh the the most notable change here is that this page has loaded in a light mode right so you should probably notice that as well if you are running light mode well no change for you but just uh to give you verification yes that is okay that was supposed to happen great so one thing that we have to do now is learn how to add components one component which we are most certainly going to add is going to be a button component but here's a little thing that i'm going to do so if you want to you can run banx dash dash ban shat cn uh and what was the version let me just find it 2.4 0.0 dash canary 17 and then you would add add and for example you would add the button right you can find the instructions for that on the shat cn website here so for example go inside of components and find the button uh i'm not going to be doing that instead i'm going to do add all so i'm only have to do this once and i am simply gonna have all components uh do you need to use all components absolutely not do you can you add them one by one definitely uh the reason i'm adding all of them here is because it's easier for me to run a tutorial uh without constantly having to remind myself oh wait what was the version that we're using right so i'm just running this one time and there we go we now have a new components folder here UI folder inside and every single component in here And you can see a lot of changes here So now let go inside of app inside of page and let's clear the entire thing. Let's remove the unnecessary import and let's return a div and let's return a button from components UI button. And inside of here, I'm going to say hello world. And besides this, some other things have changed. So the package JSON has changed, as you can see, and it installed all the radix skeletons behind each of these components right here, as well as some other useful components here. So yes, just in case you're wondering where do I get that from, also use mobile hook is something that was added. Utils remains unchanged. Okay, so just confirm that you got these things as well. So we are on the same page. And now that I've added the button here, if I do banner run dev, and if I visit my local host here again, I should just see a hello world button right here. And if I go inside of my page, I will be able to give it a variant, for example, of destructive. And now it looks like this. And in the future, we're going to go inside of this components UI button dot DSX. And this is the charm of ShadCN. It does not hide the source code from you. So if I want to, I can go ahead and just add, you know, CVA standing for code with Antonio background blue and text white like this. So then I can change this and you can see it will automatically infer that type here. And there we go. I just made a custom variant for my project. So we're going to play around with these options here, definitely. You can, of course, remove this for now. And you can see how now we immediately get the error here. So basically, that was the goal of this chapter. It was to confirm that our tailwind is working, to confirm that ShadCN is working. And one thing I'm going to do before we deploy, not deploy, before we push this to GitHub is the following. So go ahead and just write a sentence. Hello world. Like this. Mine is very big because I'm zoomed in, right? So feel free to zoom in if you have a large screen as I do. And what I want you to try is add some class names here. So for example, text rows 500 like this should make the text red. And in case you're wondering, how come I have this little handy color here? How come when I was typing, I got all of these suggestions here? How come when I hover, I can see the underlying CSS here? All of that is thanks to this package. My apologies, this extension called Tailwind CSS IntelliSense. This one right here. Make sure to update it if you haven't. So this is my version because I'm pretty sure, you know, some specific syntax definitely changed in between Tailwind version 3 and Tailwind version 4. So it's important that your extension doesn't tell you the wrong information because one thing that I use this extension for are things like invalid class. So I know that this doesn't exist because when I hover, nothing happens. Whereas when I hover here, it shows the underlying CSS. So that's a useful tip for you. Great. So for now, you can leave it exactly like this. Confirm your Tailwind is working. Confirm you have Shatsy and UI, confirm you can add components, confirm you can use those components. And now what we're going to do is we're going to go ahead and we're going to push this to GitHub. Usually I don't do this, but I figured that I should give you the whole process of building my app, which includes, you know, running this Git commands as well. So let's go ahead and do Git add and a dot, which will simply stage all the files you can see they are now inside of staged here like this then we're going to do git commit.m and inside of here you would write a descriptive message of what you just did for my convenience what i'm going to do is i'm going to simply call each commit by my chapter right so this is the chapter called zero one setup so that's how i'm going to call my commit You can, of course, be more descriptive here. This is simply something that helps me with the tutorial, right? And just press enter, and you will see that it will add all of those here. So now you can see that it has been committed, right? That's it. Well, not exactly. One problem is that this current GitHub repository, while it exists locally, it doesn't exist anywhere on the, well, we can say cloud, right? It's definitely the correct term. So head to github.com and simply create a new repository. So I'm going to go ahead and call this multi-tenant e-commerce like this, and I'm going to set it to private. And after that, I'm going to create the repository. And after that, you have two options here. So the second one is the one we are interested in, which is push an existing repository from the command line, because we already have an existing repository. So you can just paste these three lines here and push. And wait a second. There we go. And now when I refresh here, you will be able to see that we now have a proper GitHub repository here. And then we are going to be pushing each change here. And I'm also going to show you how to branch things just so you learn a bit extra stuff here in this tutorial. Great. So I believe that marks the end of the first chapter. We've set up Next.js. We have verified the versions. We have verified the config files. We tried out changing the files. We installed ShadCNUI and we learned how to add components. And we have pushed our project to GitHub. That's it. That's it for the setup chapter. And see you in the next chapter. In this chapter, our goal is to modify look and feel of our application. So in order to do that, we're going to have to change a couple of things, starting with adding a new font called DM Sans. After that, we're going to modify our globals and our components in such a way that they represent neo-brutalism style. So what is neo-brutalism? Basically, it's the style that you see in websites such as gumroad.com. And there is an amazing website and an actual component library project called neobrutalism.dev. And that's the inspiration for this project. So I highly advise that you visit this. Basically, we're going to try and recreate this ourselves. I just wanted to give a full shout out to the person and the project itself here. and the cool thing about this project is it's actually completely compatible with ShadCN and Tailwind version 4. The reason I'm not using it in this tutorial is because initially I did use it and then I switched to Tailwind version 4 and at the time this project did not support that but now it supports it so I definitely want to give it a shout out so you can check it out yourself but basically this is what we're going to try and do so take a look at this cool buttons and everything else it has but we are not going to need all of these components we're only going to need a few of them but I just wanted to give a shout out because this is the inspiration for the neo brutalism design that we're going to be building and I also want to show you the creator behind that project as you can see he also has another very cool project which is in the form of counter-strike 1.6, which is amazing. So yeah, in case you're looking for a developer, looks like he is open to work. So I just want to give a shout out. Amazing, amazing work. And now we're going to try and do this ourselves, right? So let's start by adding a DM sans font, right? So what I want to do is I want to go back to my terminal and just get the app up and running. And then let's go instead of app folder layout.tsx. And as you can see, they are actually adding Next.js team actually added a font here already. So this is what we are going to do. We're not going to be using the gaste font. Instead, we are going to add the dm sans font. So I'm going to go ahead and add const dm sans dm underscore sans subsets latin just like that and then instead of dm sans here what i'm going to do is i'm going to remove all of this here and just put dm sans dot class name right here and just by having this change when you refresh your local host you should see a new font here all right so it should be well i think it makes a good difference actually the gaste and this font do vary quite a bit so you should already be able to see a difference in the font here so that's our first and easy step right it was modifying uh the look and feel right and now we're going to continue uh into this neo-brutalism style here by modifying the globals.css where The goal is to make all of our borders completely black. But what do I mean by that? In order for us to do that, I first want to display all of our components here so we can see what is the current color of the borders. So let's go inside of our page.tsx, and I'm just gonna give this a class name of flex, flex, column, and gap, y, or four, just so we can go ahead and see individual components here. So I'm just going to wrap each in a div. So let's add a button. And this will simply say I am a button like this. Like that. Okay, so this is a button. Now let's go ahead below that and let's add an input component. Which you can import from components you input Let give it a placeholder of I am an input Then below that, let's add a progress from components UI progress. And let's give it a value of 50. So right now you can see that I'm just adding some components here. Great. then let's go ahead and let's add a text area from components ui text area and let's give it the value i am a text area like this so basically i'm just adding things here uh and if you get an error here that's completely fine so it looks like uh it's expecting an on change alongside the value that's just fine and you can see how next js has this beautiful new error display this is the first time i'm seeing it actually because i was developing on an older version of next js looks absolutely amazing the amount of detail they put in uh okay so just to make this a little bit easier to look at uh let me just add a div with a class name of padding for here just so everything is a bit pushed in there we go so now it's easier to look at all right so we have a button we have an input and we have the progress bar and we have a text area and you can already see the border which i'm talking about right in order to make this neo-brutalism like we would expect it to be kind of completely black right and instead of value let's use a placeholder here and now let's also add a checkbox from components ui checkbox and i think we can just leave it empty there we go a checkbox here so these are all the things that we are going to use so let's go ahead now and do the following basically what i want to do is i want to modify the border of my inputs here and make them completely black and technically i could do that here as you can see it says border here and it says border input here so what i could do is i could use border border here and I think that that might change it. It looks like it's not, but if I use border black, there we go. So this is what we're trying to achieve. But changing it in this way works, is not incorrect or anything, but I think there is a faster way we can do that. And we can knock off multiple items at the same time this way. So if you go inside of globals.css here, in the first part, I believe these are, you know, tailwind items. But if you scroll down here, you should see chat CN values. And inside of here, you will be able to find the border and dash dash input. And this basically uses, I think this is OKLCH. I think that's how you actually pronounce it. And this is chroma, this is hue, and this is lightness, I think. so i think in order to make this default border black all we have to do is modify this and put it at zero like this and there we go you can see that that knocked off the text area the input and the checkbox right so that's why i said that i think there's a smarter way of doing that right uh so uh now what i want to do is i want to check if there is maybe a way to reduce our radius in the same way you know just make it a little bit not so round but we can do that later because the goal was of this chapter is just to make this beginning to look like a neo-brutalism style here uh so what we're going to do now is we're going to go into individual you know components here which we have added, because these are going to be the components we will be using the most throughout our project. And we're now going to add some specific styles and variants to them. Just basically, we're going to make them look and feel like we want. So let's start with the button, because the button will get the most drastic change. So let's go inside of button. You can either command and click inside, or you can use source components UI and go inside of the button here. So what I'm going to do is I'm going to go inside of the button variants here. This is where we are going to do our magic. So let's go ahead and below link, add an elevated mode. Let's give it a background color of white. On hover, let's give it a shadow. And inside of here, it's very important that you don't add any spaces. So four pixels underscore four pixels underscore zero pixels then zero pixels again and then a color in this case the color will be black so this is basically rgb alpha so this represents black and one represents full opacity and then if we just go ahead and give this a variant of elevated there we go i think that when you hover you should already see the effect that i'm talking about right so now let's go ahead and do another thing hover translate my apologies minus translate minus x so if i say minus or dash i mean the same thing right depending on the context sometimes i will call it minus sometimes i will call it dash so hover minus translate minus x minus four pixels like this and then you can copy this paste it and change this to minus y like this and add transition all so now there we go you can see how we have a much much cooler effect now so what we have to do now is we have to add a border here right so let's go ahead inside of here and what i'm going to do is i'm just going to add my custom class names here at the end so i know that i have added them so border is going to be the first one there we go so now each of my buttons has a nice little border here and i also want to add cursor pointer so let's talk about the cursor pointer here um i'm used to buttons having a cursor pointer In the new version of Tailwind, they have removed the cursor pointer from all button elements. I believe that's an accessibility thing, right? If you like it that way, you can remove cursor pointer. But in the original app that I was inspired by, Gumroad, they have this all over the place. And I don't know, it feels correct for this to be a pointer. I'm not sure what else a pointer would be. but then again, I'm not an accessibility expert, right? Great, so that's one thing solved. The other thing I want to do is I want to find this, text small. I want my buttons by default to have text base, right? So text base will just give our buttons a bit larger font. Great, but we are not done yet. So we have added the elevated style, which we're going to use across our app. and now what I want to do is I want to search for shadow here and I'm just going to replace shadow in all of my instances here basically I don't want a single one of my buttons to ever have shadow so basically just remove that right just remove the shadow extra small instance so when you search for shadow here, it should not exist beside this elevated style. And now let's go ahead and just modify the sizes a bit. So my default will actually give this a height of 12. I want my buttons to be larger. My small will then be a height of 10. My large will have a height of 12. I don't want it to be bigger than that. And we can leave the icon as is. So now there we go. Your buttons should be much larger. Of course, this is how it's going to look like in the end. Great. So now what I want to do next is I want to focus on the input component. So let's go ahead back and let's go inside a page here inside of the input component itself. Inside of here, we're gonna go ahead and add a new class here at the end, right? So basically I want it to be easy to find, modified, just so you know that you've modified this, right? Let's give this a height of 12, background color of white, font medium, and on medium text will be base like this. So in order to do that, let's remove the height nine here. so it doesn't override itself like this. And I think we can also remove the shadow extra small. It's no longer going to need that. And we also have MD text small. You can remove this as well, even though I believe this would override itself. But let's go ahead and try it out now. There we go. So a subtle difference, but the text should now be the same size as our button here. Great. Now let's go ahead and let's go inside of the progress here. So the progress will be an easy modification here. We are going to remove the background primary with a 20% opacity. And instead, I'm going to go ahead and add modified classes here. And let's give it a border. And let's give it a background transparent here. and then in the indicator let's use background pink 400 which is going to be kind of like our primary color here so there we go this is how i want the progress bar to look like if you want you can also play around with the height of this like give it a height three but i think i found height two to be perfectly fine for what we are going to use it for you know looking like this you know in the middle of these two components maybe height three looks a little bit better. So, okay, let's change it to height three and I'm gonna come back to it later if it's problematic. But this is how I want to modify our radius, our progress. And now let's go ahead and do the text area one, which is going to be relatively simple. So instead of page, let's go inside of the text area. And inside of here, we have a text area.\nhave to find the MD here. And let's go ahead and do the same thing. Modified classes. So MD, that's the base like this. So we can remove this part. We don't need this. And I'm pretty sure that's all I wanted to do here. I'm not sure if there's anything important. Let me check. Do we have any shadow? We do have a shadow. So let's remove that shadow. We don't need it here like this. There we go, so now our text area and our inputs and our buttons all have the same font. And I don't think there's anything we have to modify in regards our checkbox here, because it was modified when we changed our globals.css right here. And instead of globals.css, it feels like we could modify our radius here somehow. But I'm not exactly sure how because it kind of changed in between what I was doing. So for example, let's go inside of a button right here and let's see. And let's search for radius or that will be rounded, right? So rounded MD and looks like it doesn't use, oh, it uses rounded MD here. So if I were to go into globals, I assume that this is what would be changed here. So it uses the radius, which is 0.25 rem. So two pixels. So if I change this to minus four, would it be less round or more round? Let's do two again. Okay, yeah. So I think that, yeah, the more you reduce this, the more you increase these pixels, the less around it's going to be. So I'm going to make it minus four pixels. I think by default, it was two pixels. So I'm going to change the radius MD to be minus four pixels. The reason I'm doing it here is because I'm pretty sure that I want this to be the standard everywhere, right? So I don't have to go through every single component and change it there. I can just do it in this one place. And I think these are the main components that we are going to need because everything else will be styled in a different way. We are going to create our own components for that. But these are some of the components which we're going to reuse throughout the project the most. So that's why I wanted to create kind of a style guide for them. And you can see how nice this Neo Brutalism style already looks pretty unique and easy to look at and it will be very easy to build with so now what we have to do we modify the global css we uh modified and displayed these components and now we have to push to github so let's go ahead and do the following i'm going to shut down my app i'm going to add everything here uh and before I do anything else, I'm going to get checkout B and I'm going to call this zero to customization. You can, of course, call this however you want. I'm going to, you know, stay true to this. You don't have to do this, of course. I'm just trying to teach you, you know, how to branch out. So there we go. So now you are on another branch, as you can see here. So let me just repeat git add here. And then I'm going to do git commit zero to customization like this. and then I'm going to get push dash u origin o2 customization so now a new branch is pushed to my github here and there we go I can now create a pull request here you should have the exact same message here in case it wasn't you can go in pull requests click new pull request and in the compare select your new branch and then simply create a pull request like this so in here you can see all the changes that you've made right so what we are going to do now is we're just going to create a pull request here uh and by default uh nothing should appear here to you but as you can see i have this thing called CodeRabbit. If you ever worked in a team, you probably know that branches and pull requests are never really reviewed solo, right? You always have a team of reviewers who look over your code and leave comments for you if there's something that you need to change. Since I work solo, I decided to give CodeRabbit a try, which is basically like your little let's call him an AI employee. So what CodeRabbit is doing right now for me is it's going to look throughout all of my files, which I have changed, and it's going to give me a summary of what happened. And also it's going to leave some comments if I want to apply them. And what's cool about CodeRabbit, if you install CodeRabbit, you can just search for CodeRabbit AI. I'll probably leave a link in the description as well if you look through the tips here you will find different ways to kind of chat with code rabbit so you can like directly reply to a review comment and then you can tell code rabbit hey i fixed this can you check it again or you can tag it somewhere right so i could kind of go here for example and i could select this line and i could tag, whoops, let me go back. So I could kind of select a line here and I could tag code rabbit and then I could ask it to do something, right? Review this or would you do this in a different way? There we go. So now as you can see, I have, oh, my apologies. I'm in a different pull request. That's my pull request here. So I'm just going to pause the screen and let the rabbit review and show you the results. And there we go. So after a couple of minutes, CodeRabbit generated a summary. So new features. We launched an enhanced homepage with interactive elements, including buttons, inputs, progress indicators, text areas, and checkboxes. And it noticed that we refined the global visual design with updated typography and improved UI elements. styling we enhanced the button sizing with new hover effects and updated styling for inputs progress indicators and text areas to deliver a modern and consistent look which is exactly what we did and you can see it actually comments a walkthrough of exactly what we did so this is super useful it noticed that we modified and gave a new font here it noticed exactly what we did with our UI here and our component styling, removing shadows, adding the elevated variant, input has a modified height, the background and font weight, progress bar has increased in height. So it pretty much noticed every single thing that we did. And you can see it also tells you, you know, that some other things are happening in your code as well. It will also leave comments here, for example, in the text area. It's recommending extracting or organizing the lengthy class string. So a bunch of useful things. Basically, if you're a solo developer or if you just want to improve your skills by listening to an AI tell you what you could improve, a very, very useful feature. Great. So I'm currently satisfied with what I have here. So what I'm going to do next is I'm going to merge the pull request and I'm going to confirm the merge. And usually after you, you know, successfully merge a branch, which is a feature branch, you would delete it. But in my case, since this is a repository that I'm going to share later with you, which you can access through my website, through my platform, I'm not going to delete my branches simply so you can later go ahead and look what I did step by step. Great. And then when you've done that, make sure to check out back to your main or master branch and make sure to get pool origin master. So this way, that branch, which you just merged is now active. So now we can very easily check whether that is true or not by doing npm run dev going back to localhost here and it should look exactly the same because we successfully merged our branch you can also go inside of your ide here and confirm inside of the graph here what happened so we had a setup we opened a new branch called customization and then we merge that back here so yours should look the same of course it's not required to follow Git at all during this tutorial. I just want to bring a closer to real world like experience when building this project. Great. So I think that marks the end of push to GitHub task as well. Great. Amazing, amazing job. In this chapter, our goal is to create the home layout. As you can see from this screenshot right here, the home layout will consist of three main parts. We are first going to have the nav bar, which is going to feature your app, your marketplace, your platform's logo, some basic navigation bars where you will be able to add the about page and some basic, you know, SEO pages, let's call them that. And then two buttons, one for login and one for creating an account. We are then also going to add a search bar here, just so we actually use one of the components which we previously created. And for now, we're going to skip this part, which are going to be the categories, simply because the categories will be a much more complex component than everything else we're going to build in this chapter. Besides this, we are also going to have to create the equivalent on mobile here. And we're also going to add a very simple footer. So I'm gonna be calling this app Fun Road, which is a parody on Gumroad. You are of course feel free to name this whatever you like So our goal is to add a home route group where we going to be able to do all of that If you don know what route groups are don't worry, I'm gonna do my best to explain that. And then we're just gonna do and push to GitHub. So before you can start, it is crucial that you establish that you are on your main or master branch. You can do that by taking a look here in your IDE, especially if you're using Visual Studio Code. You can also click here to confirm that you are on master or main. And you can also type git status here. I just want to ensure that you are not accidentally on a previous checked out branch. Great. So now let's go inside of source. Let's go inside of the app folder and let's learn how to create a new route. So if I create a new folder called test and inside a file name called page, and then I do a default export here and write div test page here. What do you think will happen if I go to localhost 3000 slash test? Well, I think you can already guess. If I go to localhost 3000 slash test and of course run my app first so I don't embarrass myself. let's refresh and there we go we can now load the test page so that was a super quick crash course on how routing works in next js basically when using the app router which in the shortest definition means you're using the app folder if you create a new folder and you put a reserved file name page it can be .ts, .tsx, .js or .jsx basically paged is the important part and the second important part is the default export so if I don't do this and instead do a named export what will happen is an error the default export is not a react component in test page so that's what it's looking for whenever you want to create a new page in next.js you need to do a default export here great so now you learn how to do that and now you've also learned that whenever you have a page with a default export and the folder that is going to become a part of your url but what if you just want to organize your files without creating a new route you can do that using a group for example home like this so if i go ahead now uh and for example create another route here let's call this foo and then a page tsx inside let's go ahead and render foo and also the name of the default export does not matter it can be page it can be x it absolutely doesn't matter so where do you think i can now access this foo page by some logic it should be slash home in parentheses and then slash foo. So let's try that. If I go slash home slash foo, I get a 404. This page doesn't exist. So what I just attempted was slash home and then slash foo. That's what I attempted. But the truth is, whenever you add a folder inside of parentheses, that is a convention for a route group. That basically means that you're going to use this folder as an organizational folder, or more precisely, and the better definition to use would be a route group because the route group has some special quirks in it. One of the quirks is that it's going to be omitted from the URL. So this is how you're going to access it. So if I now go to localhost 3000 slash foo, there we go. I can now see the full page. So this is now gonna come quite handy for us. So this is what I suggest you do now. Go ahead and shut down your app just for a second. Go inside of your app folder and in here, remove the test folder entirely. And now what I want you to do is remove the full page as well. And instead, I want you to copy the page.psx and I want you to paste it inside of the home folder. You can also drag it and move it, but that's going to trigger some Next.js cache to be unsaved and it often just confuses people. So now that you've copied and pasted that inside of the homepage, you can delete the old one. So what I've done now is I've moved my page inside of a home here. And this basically allows me to organize my routes in a better way. So what I can do now is besides having a root layout, so this file, as you can see, is quite structurally important. It provides us with the HTML and the body wrappers around our entire app. So I don't want to modify this file and I don't want to create another reserved layout file. But what I can do is use this route group to create another layout file in here. layout file has similar rules to page where you need to do a default export and let's try this let's go ahead and return a div with a layout and the reserved file name layout.tsx if you go to localhost 3000 but of course make sure you now run your app the reason i told you to shut it down is because sometimes it can cause hot reload issues when you move your main page.tsx into a route group. So what happened now? Why do I only see layout on my localhost 3000? The reason I only see layout is because layout is rendered first. And in order to do a proper layout, you need to do the same thing that you do in your root layout here. You need to pass the children. So let's go ahead and create an interface. layout or just props children react react node i'm going to go ahead and extract the props here and extract the children and then render the children here where do we get the children from next js automatically fills that prop you don't have to worry about it there we go we now bought our app back to its initial state so what did we achieve with this layout file how is this useful in any way well here's how for example you can now add a navbar here and you can give it a class name background red and a text of white and some padding and let's say navbar there we go you can now see a navbar above my entire content And the cool thing is that inside of this route group, which now has this layout, I can create sub routes like foo, like I did previously. So let's just add a page inside. Foo page, it will also have a navbar. So I'm that confident that when I visit foo, it's also going to have this navbar. So if I go to slash foo now, there we go. you can see that my full page also has a navbar. So that's the power of layouts. They allow you to create reusable wrappers, components, everything you need inside of your Next.js application. They also don't re-render when you're using navigation. So if you're navigating within this route group, so if I go from page to foo using the next link, this part will not re-render because it doesn't need to, right? It's reusable, it's the same on both routes. So that's what's cool about layouts. And that's also what's cool about route groups. So as I've shown you in the beginning, in the app directory, nested folders are normally mapped to URL paths. However, you can mark a folder as a route group to prevent the folder from being included in the route's URL path. This is the convention, as we've just learned. And as you can see, you can use it to organize or separate your modules like marketing and shop. Or you can also share layout files in between them like we are doing right now. So that's a quick crash course on routing. Now I'm going to remove my full route again. And I'm going to go back inside of my layout file. Inside of the layout file, let's go ahead and give this div a class name. flex, flex column, and a minimum height of screen. And now replace this with an actual navbar component. You are not going to have it, so an error is expected at this point. So now let's go ahead and let's create our navbar component. For starters, let's put it in here inside of the home route group. So navbar.esx like this. and when it comes to components we can use and we should use named exports here they are much easier to work with and they won't accidentally be you are reducing your chances of accidentally creating a reserved file name with the default export right so better to use named exports when it comes to components so inside of the nav bar here let's go ahead and We'll not use div, right? Let's use nov and give it a class name. Height of 20, flex, border bottom, justify between, font medium, and background color of white. Now let's go inside of the layout and let's import the novbar from .slash novbar. I'm going to go back to localhost 3000 and I'm going to zoom out a bit. So you can now just see a big space for the nav bar, but no text inside of it just yet. So what I want to do is I want to go ahead and I want to import poppins from NextFontGoogle. And now I'm going to prepare poppins here in a constant. So poppins. And I'm going to add subsets, Latin. And weight here is going to be 700 because that the only weight we are going to need basically the ultra bold one And now let go ahead and do the following inside of here i going to add a link from next slash link which will simply uh route back to the root page later on that going to be useful right now it doesn't make too much sense but let's go ahead and give it an items uh actually we just need pl6 like that and let's add a span here fun road so right now you should see a text which says fun road and it's a link you can see when you click on it right and now let's go ahead and give this span a class name and let's use cn which i briefly mentioned in the beginning which stands for class names and it allows us to merge tailwind classes so i'm going to go ahead and give it some default classes i'm going to give it an extra large text and font of semi-bold so that's how it looks by default and then just for fun we can add a dynamic class name so poppins font class name so now the font looks just a little bit different uh as opposed to all other uh all other well uh texts right let's go ahead and give this link uh an items center as well uh actually yeah that doesn't do anything at the moment uh we need to add flex item center maybe there we go so flex item center and then the fun row logo is center it's no longer attached to the top great now what i want to do is i want to create an individual navbar item which is basically this right individual item here so the way i'm going to do that is just in the same component i'm going to create a const navbar item and let me just fix the typo here so navbar item. And inside of here, I'm going to be using a button component from components UI button. And you can see I have a certain convention when it comes to imports. So I'm using the global imports, basically my NPM packages at the top. And then I use a empty white, empty line to separate from my alias imports here. So now that we have the navbar item, let's create an interface navbar item props and let's go ahead and give it an href let's give it children and is active which can be optional like this let's go ahead and destructure navbar item props so we're going to have href children and is active and now I'm just going to go ahead and render the children inside for now. So I'm not going to do anything else. I'm not going to use anything else. So now below this, let's create const navbar items, which is going to be an array of objects. In the first object, we're going to have an hrep of slash and children, which is just going to be a text home. And then what you can do is just add as many routes as you want. So slash about, slash features, slash pricing, and slash content. This part is not that important because this is not where our main e-commerce logic is going to happen. All of these are mostly SEO pages, right? So it really doesn't matter what you put here. So just put, you know, a few of them so it looks nice from the homepage. Now what we're going to do is we're going to go and just... You can use white space if it's easier for your eyes. Create a div here and the class name items center gap for hidden and flex on large devices. So hidden by default, but when it hits a large breakpoint, it becomes a flex. Inside, we are going to map over navbar items, get the individual item, and render the navbar item component. inside of here give it a key which is going to be item.href so make sure that each href is different and i think you can just spread the rest of the item inside the props should match the href and the children and we're going to have the handle is active separately and this is actually not a self-closing tag. So I'm not sure if it will work the same way if you pass the children as a prop and if you actually pass it right here, but just to be safe, let's do it like this for now. So now you should have in this corner right here, all of these buttons, right? And now we're going to go ahead and make them look better. So let's go back inside of the navbar item and let's focus on making it look nice first of all i'm going to give it a variant outline so that's how it will look uh by default now and then let's give it a class name here using cn so let's add some default classes by default the background is going to be transparent so regardless of uh what the actual background is we're going to set it to transparent we are also going to make sure that when we hover it stays transparent we're going to turn them into full rounded buttons on hover we're going to change the border to primary and by default border will be transparent like this so when you hover it's going to start to show the border. Go ahead and add px of 3.5 and the text of large inside and then add a comma and then a special case if is active. In that case background color will be black, text will be white. On hover background will stay black and on hover text will stay white as well. There we go and now let's go ahead and wrap our children inside of a link which we already have imported and give it an href of href and in order to make this work properly you also have to pass as child prop to the button which basically tells to the button that this link will now be used as this component. And there we go. This is how it looks like now. So in order to properly show that is active prop, what we have to do is we have to use the path name hook. So const path name, use path name. And once you do this, make sure you import the path name from next navigation, you are going to get an error. There we go. You are importing a component that needs use path name. This react hook only works in a client component. To fix, mark the file or its parent with the use client directive. So basically in the next JS, all components and pages inside of the app folder are server components by default, which means that when we want to go back to the good old client components, we have to, let's call it open a portal to the client, right? It's a better term to use than to convert a component to client because people don't know this but uh you can also mark a parent component as a used client and then everything inside of it will become a client as well right so if but i'm also going to show you a little trick so if you go ahead and mark this as use client this will fix the issue right no longer no more errors right now if i remove it you can see that it causes errors and if you remove it from the navbar and go inside of layout here since you render navbar like this it will also work if you mark the layout with use client as well as you can see right here but if i remove it we get an error right so what we did with the layout here is this suggestion, it's parent. So when I show this to people, they immediately think, oh, okay. So if you mark your layout file as use client, every single thing from now on in its children will be client. That is not true. So you can render a server component inside of a client portal. That's why it's a better terminology to use portal, because you're not converting a client a component into a client component you are just opening a portal in this specific uh place right so what you can do now is the following let's let's try it like this so i'm going a bit off topic just to you know cover this important topic that we have inside of my page.tsx which says i am a button i'm gonna go ahead and do the same thing i'm gonna do use effect console log rendered so this will be rendered only on the client make sure you import use effect from react and i'm going to remove the use path name from my nav bar and from the import just so it doesn't mess with my example now you don't have to do this i'm just demonstrating something right now as you can see my page dot tsx right here has an error i'm trying to use use effect here but i need use client easy fix right just use client there we go so you might be thinking okay but i could have also done it here in the layout right because i know that inside of my children the page is a client component oh well that's not true that's because if you render something through the children prop that will not be passed through the portal right so that's going to be a server portal if you want to think of it that way so i just wanted to clear that up uh so we're going to remove the use client from the layout because we don't need everything else to be a client we're going to remove the use effect from our home and i'm going to close this page By now, you should have no errors at all. Let's go back inside of the nav bar. Let's uncomment this. And let's uncomment the use path name. Now let simply add use client here because that the only place where we need useClient at the moment No need to remove server functionality server components from other components right And also, server components are not the same thing as server-side rendering. So just because you mark something as useClient does not mean that it's not rendered, that it's not doing server-side rendering on it. It is still doing server-side rendering on it. It's just not a server component. Those are two completely different things. All right, now we should fix the error. And let's go ahead and do the following. Inside of the iteration here, let's add is active. And let's do the following. So we're going to do if path name is identical to item.href. So right now, home should be highlighted because we are on the root path. So if you want to, you can now go ahead and create about and inside page.tsx. Return a div about page. So that's going to be the most what I'm going to do about these SEO pages because I don't think anyone here is watching to see me design these pages. they're going to be different for every single e-commerce. So it doesn't make sense, but I just want to show you the skeleton behind it. And we are going to focus more on the actual e-commerce application rather than this SEO things. I don't think anyone's really interested in this. So we already have about, what's the next one? Let's see. Features. And then we have contact. So inside of contact, this will be contact page. Inside of features, this will be features page. And inside of pricing, this will be pricing page. There we go. So now you should be able to click on all of this. And you can see how your navbar is getting highlighted. Great. So later on, when you finish the project, you can develop these pages fully, depending on what you want your e-commerce to be. I'm just giving you a starting point here. It really doesn't make sense for me to develop those. what i'm going to be developing is the actual shop right not the seo of the shop so let's go ahead now and go back inside of the navbar here and what we are going to do now is we're going to add these two buttons right here which will allow the user to log in or register so we're going to do that after this part right here. So let's go ahead and do a div. And we're going to give class name hidden by default, visible on large devices. So yeah, I forgot to tell you this. In case you're not seeing any of the items, it's because you're zoomed in too much or you're using too small of a screen. So just zoom out a bit or expand your browser because we hide these elements on mobile mode. So make sure you have enough space to see them and now inside of here we're going to render two buttons the first one will say login and the second one will say start selling there we go login and start selling you can see how now everything is nicely aligned so now obviously we're going to modify these so they look a little bit better let's give this a variant of secondary and let's give it a class name border l border top 0, border bottom 0, border right 0, px of 12, height of full, rounded, none, background color of white, on hover, background color of pink 400, transition, colors, and text large. So now you can see the effect right here. so you can now copy this because it's going to be very similar for the button below like that with a slight modification by default this will have a background color of black and a text of white on hover it's going to be background pink 400 and on hover is going to be text black like this so now this is how they look like great so what i'm also going to do is add a link which we already have imported for the login is going to go to slash sign in and for this one it's going to go to slash sign up like this and give both of them as child so the links are properly rendered there we go so if you click on any of them you should get a 404 because we have not developed the login pages just yet great so what we've done now is we've finished the desktop part of this right here and now we have to do the mobile part of it. So let's go ahead and create a component called navbar sidebar. And we can do that right here in the home route group. So go ahead and create the navbar dash sidebar dot TSX component. This will be the props. And we also have to create an individual navbar item interface so basically an href and children and then let's just export const navbar sidebar like this and let's distract all the items we need so items open and on open change from our props like this and the component we are going to be using is the sheet component from chat cnui you You can also use the sidebar component, but somehow I've gotten the habit of using this. Sometimes it's actually simpler to use it, but the sidebar component is amazing, right? So you can check it out on Chatzee and documentation. We're also gonna be using scroll area. So let's go ahead and actually use that here. So we need to return a sheet component and go ahead and give it an open prop of open and on open change, on open change. And inside sheet content with a side left and the class name, adding of zero and transition none. And inside of here, you can add a header with a class name, adding for and border bottom and a div with a title menu. And you can give the title a class name, my apologies, the div a class name of Flex and Items Center. This is because I plan on also adding a close button here, but now that I think of it, maybe the close button will appear by itself. Anyway, this is enough for us to try it out. It's not finished, but it's enough for us to see what we're doing, right? So it's important that you pass the open and unopened change here. And now what I'm going to do is just anywhere here, it really doesn't matter. I'm gonna do it below the link, render the navbar sidebar component. So you can import it from .slash navbar sidebar like this. and now you also have to create some states here so const is sidebar open set is sidebar open use state from react with a default value of false make sure you have imported use state from react and now what you're going to do is you're going to pass open to be is sidebar open on open change set is sidebar open and you can also pass in the items to be navbar items which is the constant which we have defined right here and now when you try going to your app nothing should change but if you change this to true there we go you can see you have an open menu now so now we have to create a button which is going to open that so let's go all the way down and let's take a look at this so it's hidden by default but it's flex on large devices so we have to do the opposite of that here by default it's going to be flex but when it hits a large device breakpoint it's going to be hidden and let's also add item center and justify center and inside a button component with a menu icon from lucid react so make sure you have imported lucid react here now let's go ahead and give this a variant of ghost a class name of size 12 border transparent and background color of white and on click is very simply going to be an arrow method which calls set is sidebar open and sets it to true so right now it's not visible but if you zoom in a lot or use inspect element to shorten your page. There we go. You can see it. So that's how the mobile view is going to look like. There we go. You can, of course, play around and increase the size of this if you want to. So I think the way you have to do it is by using this maybe. Actually, I'm not 100% sure. So I'm just going to leave it as is right now. great so now we can go back inside of the navbar sidebar and we can actually complete it so now we have the items make sure you have passed the items here through the prop right here and we're going to do a similar iteration over all of our items here so let's go outside of the header and let's add a scroll area which we imported let's give the scroll area a class name flex flex column overflow y auto\nheight full and padding bottom of two. Inside of here, we are iterating over our items. So let's add a link from next link with a few props, item href as key and href item href. Make sure you have imported the link from next link like this. Inside of the link, we are going to render the children. So our link is the main component here, so we can give it direct class names here. Full width, text left, padding of four, hover, background black, hover, text white, flex, items, center, text base, and font medium. Like this. So now already when you click here, you should see all of the elements here. great and then we're just gonna go ahead and add two separate items so class name border top here and you can go ahead and just add two buttons here my apologies link components not buttons this one will be sign in like this and the lower one will be sign up So now I'm just going to copy the class name and paste it here and paste it here as well. So this will be login and this will be start selling. So now you should have the exact same components right here. And you can see we also have the close button. So yeah, we don't actually have to do this. No need for that. Let's try it again. There we go. so we can now click on login we should get 404 but when we click on these we should get redirected to those pages uh and here's what we can do uh to make this just a little bit better when you click on this link let's also do on open change false basically we're going to close uh this every time we click on some element. So when we click on home, it closes like this. There we go. Great. So that's it for the first part. We added the nav bar and the mobile nav bar. And now we have to add the footer. So since the chapter is already almost 40 minutes long, I'm going to leave the search bar to be finished later on with the categories, right? And it actually makes sense because this is not really a layout component. It's a component specific to the homepage. So it makes sense that we only focus on this part for now. So now we just have to do the footer, which is way simpler than anything we've done so far. We are going to go back inside of the layout file right here. and we're just going to add a footer at the end here. Footer does not exist, so an error is expected. So in the same place here in the home route group, you can just add footer.tsx. Let's export const footer here. And instead of using div, let's use footer and the class name flex border top justify between font medium and the padding of six like this inside a div with a paragraph fun road again or you can add incorporated or whatever you want you know it's just a parody in my case in your case it can be the name of your shop or your company there we go so this is a footer and we can now import the footer right here and here's a little bit of a problem now so the footer here appears to take up all the space which is not something we want. And here's an easy way to fix that. So we can add a div around the children. And since we mark this entire thing as flex, we can add flex one to it. And now, as you can see, the children take up as much space as it can. And we can also change the background color slightly to F4, F4, F0 here. So now this has a slight color change And we also noticed an interesting issue here, which we are going to resolve in a second now. So let's go ahead and do the following. So I'm going to go inside of text area, inside of my components, inside of my source components UI text area. And I'm going to give this, let's see, background is set to transparent. So I'm going to remove that class name. And instead, I'm going to do background white right here. There we go. So that's resolved. let's go inside of the checkbox uh background dark input can i add background white here there we go that seems to resolve it and the same thing we can do in progress so i know i'm the one who put background transparent but i think it makes more sense to have it background white so it's consistent with all other you know inputs here then that kind of sums up our layout right So great. Let's go ahead and mark all of these things as complete. So we successfully added the home layout, the navbar, the mobile navbar, and the footer. So what we have to do now is we have to push those changes to GitHub. And just to say something in advance, we're not going to just randomly have these components inside of our route group all over the place like this. We're going to have a proper modules folder, but we're going to do that later on. From now, I just wanted to focus on wrapping up the layout. So last time I showed you how to check out using the CLI. So I thought that maybe this time we could try using IDE but then I remembered I have no idea if all of you are using Visual Studio Code. But yes, if you want to, you can also create branches from here. So this is what I'm going to do. As you can see, I have 13 changes. So I'm going to shut down my app. I'm going to do git checkout dash B zero three. And this is home layout. Right. So I'm just staying true to my chapters. Home layout like this. Git add, git commit. You can, of course, add a descriptive message. In my case, this will just say home layout like this. and git push u origin 03 home layout and that will push a new branch to your github there we go you can see that now i am on this branch you can see it right here so i'm no longer on master so i'm going to go ahead inside of github.com inside of my multi-tenant e-commerce repository here and there we go so again you can go inside of pull requests new pull request and select the home layout here basically your newest branch you can see a quick preview of all of your changes here and then you can finally create a pull request so before we merge this i'm going to pause the screen and i'm going to show you uh what code rabbit has to say about this changes as you can see, it just started taking a look at all the files I have modified. And here we go. As you can see, a very impressively accurate summary by CodeRabbit. So we introduced a couple of new features, including several new pages about contact features and pricing. We also added a responsive application layout featuring a navigation bar, a sidebar menu, and the footer showcasing the company branding. We've also modified the style, enhancing the look of key UI elements. So we've updated the checkboxes, progress bars, and text areas to adopt a cleaner white background for improved visual consistency. Exactly what we did. Perfect. So if I refresh, let's see if anything's more here. There we go. So we have the entire walkthrough of the changes which we've made. And we also have a sequence diagram rendered here for us so we can see exactly what's happening in our current layout. I think this is super impressive. So we can see exactly while the user is requesting a page view through layout, the layout renders the navbar. It also renders the page content and it renders the footer. And we finally return the complete page to the user. We can also see our navbar sidebar diagram here. So this is super impressive for something that is literally, you know, just installing into your GitHub. And you can even see it left some useful comments. You can see, oh, sidebar should be closed by default. It actually caught a bug. This is actually impressive. It caught a bug. Yes, I completely forgot to close the sidebar. I'm now super impressed by this. It also allows me to commit that through it as well. This is very, very cool. Let's immediately fix that. There we go. So CodeRabbit just saved us here. So I think we have to go inside of the navbar component and yeah, change this to false like this. so i'm just going to go ahead and do git add i'm still in my branch 0 3 home layout that's important right because we are fixing an issue and i'm going to do 0 3 home layout and then i'm just going to do fix and let's do we can just do git push now we no longer have to do the git push origin because you already pushed that. So now the cool thing is that I think, I'm not sure if it's going to trigger CodeRabbit again, but I think it is and I think that now CodeRabbit will basically tell us that it okay we fixed that issue But this is insanely cool It caught a bug I am super impressed. So I'm not gonna wait for the next one because, well, I know what the issue was, right? So yours is gonna be green. So just click merge pull request, confirm merge right here. And there we go. So I'm not going to delete the branch. Basically, I want you to be able to see all of them. So 02 customization, 03 home layout. And now that you've done that, what you have to do is you have to get checkout back to master or main, depending on what you use. If you get an error, you're obviously using main. And then git fetch, git pull origin master or main, depending on what you're doing. so now uh let me go ahead and close this go inside of here and there we go as you can see um we've opened uh a new branch home layout and then inside of here we've done uh the home layout fix and then we merged that pull request right here excellent so just make sure that you're on the master branch run git status and everything should be completely fine perfect great great job let's just go ahead and mark this as complete as well and if you want to you can just do bond run dev to double check i always like to be sure that everything is fine there we go Everything seems to be completely fine. Perfect. Amazing, amazing job. In this chapter, our goal is to integrate payload into our Next.js application. Before we go forward, I would like to confirm that you are on your main or master branch. You can do that by running git status and just confirming that everything is up to date. You can also double check by going inside of your graph and just confirm that you have everything on your main branch here, meaning you've merged the pull request, right? I just don't want you to continue developing in a branch from a previous chapter. Or if you're not following these Git instructions, no need to do anything at all. So now that we have confirmed that, let's go ahead and decide which database we are going to use. So Payload offers us these three amazing options, MongoDB, Postgres, and SQLite. In my case, I'm going to go with MongoDB, mostly for this reason right here. MongoDB is non-relational, meaning by nature, it's flexible having a schema-less structure. This is great for nested and relational data and specifically good for our content management system where we are going to have collections which are going to drastically change. And they are honestly going to be different for every single use case, right? So it's just easier to work with a database which can handle so many changes quickly rather than working with a database which is going to require constant migrations. That isn't to say that you cannot use Postgres or SQLite. You absolutely can. But I'm just giving you a tip. My developer experience was absolutely the best with Mongo. so uh one more point for mongo here goes for their free cloud hosting via atlas and that's what we're going to do now of course if you want to locally you can use docker if you're handy with it and just obtain a simple mongo url but i'm going to show you how to do it using atlas so Head to MongoDB websites and go ahead and open the dashboard. Depending on if this is your new account, you might see a different screen, but basically you have to get to this screen where you're going to deploy your cluster. Go ahead and select the free option and you can uncheck the preload sample data set. We are not going to need it. The provider, region, none of this matters. I think it's going to choose the one recommended for you. right? And click create deployment. No need to add anything else. While that is creating, you can close any models that show up and go inside of your database access here. As you can see, I already have a user. You might not have a user. So just go ahead and click add a new database user, choose the password authentication method. And I suggest you just add a very simple username and password combination for development, of course. And go ahead and add an Atlas admin role, which will simply allow this user to do anything. And click add user. So just confirm that you have at least one user here, which we are going to use to connect to the database. Another important thing is the network access. So at the moment, only my IP address will be able to connect to this database. but for those of you who are working from different places or you have dynamic IP addresses, you might benefit from adding another IP address and clicking allow access from anywhere or if there is no button, just enter 0.0.0 slash zero and click confirm. I would not recommend using this in production but it is definitely handy in development. That's why I'm gonna be adding it So we never get, you know, those error messages saying that we can't connect to our URL string. So by the time we did this, I think our cluster should be ready now. We can see it's still doing some final changes here. And it looks like it is done. I can click Browse Collections. In my case, it's going to be completely empty. If you added a sample data set, you might have some data here. It really does not matter. Mongo is very flexible, so we can add more data here. So leave it like this for now. And what we have to do next is we have to integrate payload, right? So what is payload? So from there, documentation, payload is the Next.js full stack framework. And honestly, I couldn't agree more about this statement. It is absolutely amazing. And with literally running a single command, this is everything that we are going to get. A full admin panel with server and client components, which is going to match our data. And we're going to be able to extend this admin panel with our own React components if we want to do that. We're also going to get automatic database schema, including direct database access and ownership, migrations, transactions, indexing, and more. And in order to access that data, Payload offers us a REST API, a GraphQL, and a straight-to-database Node.js APIs. For example, we can use those in server components. We also get out-of-the-box authentication with Payload, and we can use it and we will use it for our entire app. Also, a very important point, it allows a deeply customizable access control pattern, which will come in handy because we are going to build a multi-tenant app, which means that we are going to have to restrict some users from seeing information they should not be seeing on our admin panel. And a ton of other things we are going to discover as we go along. And in case you were wondering, yes, Payload is open source and we can deploy it anywhere. And that's exactly what we are going to do. So we're going to use the open source payload version and we are going to self-host it on Vercel. That's exactly what we can do. And it is as simple as just deploying your entire app on Vercel. There is literally nothing more you have to do. It's honestly such an amazing value that you get from this. So let's go ahead and learn how to install this. First of all, software requirements. so any javascript package manager as you can see several are mentioned here but bun is not that's because bun is still in their experimental testing phase but i'm going to be using bun i found no issues whatsoever uh this important point node.js version 20.9.0 that is important because uh next JS requires a lower version of 18.18. So if you had 19 and you thought, oh, okay, I don't have to update. Well, now you do. So this is the minimum version you need to have. As you can see, I barely make it. So I'm gonna have to update soon as well. And we already have the compatible database. So that part is solved. Make sure you have all of these requirements. So now we're going to run the Create Payload app. And before you do that, I just want to show you the current version that I'm going to be using, which is 3.33.0. I don't think that you have to use this exact version. I think the major version might be more important, right? Because payload two to payload three was a huge jump and a very big breaking change. But I think that this minor versions will not cause that much of a difference. but if you want to be sure, you can use the same version that I am. One more very important thing, you need to add this to your CLI, right? Because otherwise, it will fall back to PNPM. So this is what we're going to do now. And you might notice that there is no bun. But if I searched throughout their create payload app and I found use bun to be, you know, alongside all of these other options, but you can see they've marked it as experimental. So that's probably why they haven't added it into the readme. But in case you forgot to add this it will fall back to PNPM if you have PNPM installed And it might even give you an error if your PNPM is not up to date So I just want to give you that little tip. And it will also create a PNPM log file here if you don't specify your package manager. So make sure that you specify it. uh so what we're going to do now is we're going to run the command right so i'm going to do bun x create payload app and this is not enough right so i also have to do dash dash use bun same goes for you if you're using npx just add use npm here or if you're using yarn just add use yarn here or if you're using pnpm and pnpx you i'm pretty sure you don't have to do anything but still add use PNPN. Make sure you are inside of your project here. Make sure the project is shut down for the moment and you can just press enter. So it detected a Next.js project and it's asking if we would like to install Payload. I'm going to select yes and looks like we have to do something first. So Payload does not support a top level layout file in the app directory. So what we have to do is we have to create a new route group and move all of our files into that. So they recommend using app here. So I would suggest that you read what they recommend and go inside of the app folder and create a new folder app inside of parentheses, of course. And then you are going to select everything else and move it inside. If you get prompted to update any imports, you can just say yes, right? It really doesn't matter. so you should see about 24 changes now well i mean if you've been following you should see exactly 24 changes except if you didn't add these routes right maybe you didn't do that but you should see something like this and before you do anything else i suggest that you do bun run dev just to confirm that your app is exactly as it should be right so nothing should change you should still have your navbar and your footer which is what we did in the previous chapter and then we merge that into main or master branch correct so after you have confirmed that everything is working fine you can go ahead and run this command again just make sure you add your package manager here otherwise it will fall back to using pnpn so let's go ahead and install payload and now we have to select a database. So I will select MongoDB. And as you can see right here, it offers you a local string. But since we already created our cluster, I'm going to go ahead and click connect. There are many options you can use, but I'm going to click this MongoDB for VS Code. And I will just copy this. I will remove this. And then I'm going to paste it here. i am now going to go ahead and replace my password and my username so i've created some simple accounts antonio with a password of antonio so it's for this convenience right and you can also i think you actually should go to the end of this slash and add the name of your database so e-commerce like this. As you can see, it's using BUN and it says BUN support is untested. So this is kind of a confirmation that it is using BUN. And there we go. Payload project successfully initialized. And let's go ahead and see what actually happened here. So you can see that inside of my app folder, I now have a front-end folder, a payload folder, and my route folder. Outside of the app folder, I now have collections. I also have payload types and payload config. And what I wanted to confirm is that it used my bun lock, right? So if you would not have specified use bun, it will fall back to PNPM. And if you have PNPM installed, it would have added a PNPM lock file here, which just doesn't make sense, you know, because we want to use our current package manager. No point in using two of them in a single repository, right? So now that we have that, what happens if you try running your app? Well, I will tell you immediately, while this works, you will get an error if you refresh your homepage. The reason you're getting an error is because Payload added this front end and it added a page and a layout. So now you have two layouts, right? And two pages. So here's what I want you to do. I want you to double check that inside of your app folder, inside of your layout here, you have the DM Sans import, just because this will confirm to you that this is your old layout, which we worked on. And inside of your home, confirm that you have the page.tsx with the IMA button text and the checkbox and the text area and the progress. I just want you to confirm that all of that is inside of your app inside of the app and now you can safely remove the front end. The reason I'm asking you to check this is because you know sometimes people go ahead and do their own thing here and here when I told you to move your things to an app directory, maybe there's a chance someone actually renamed it to frontend and then payload would technically override that folder. So that's why I wanted to confirm with you that everything is working. So now if you go ahead and do bun run dev again, I would suggest you shut down your app and restart it. Let's refresh again. And there we go. everything is working so what's new well if you go to localhost 3000 slash admin which i'm going to show you here so this is the route localhost 3000 slash admin go here you will notice uh yeah you might get an error here it is possible so keep your eye on the terminal if everything is loading fine, this basically means that your MongoDB is successfully connected. So you can check inside of your dot environment here. You can see added by payload. This is our MongoDB connection, Antonio at Antonio with e-commerce here, right? So if you got an error here, it could say that it failed to connect to MongoDB. That would mean that you have provided it with an incorrect database URL. Easy fix. Just go ahead here and double check. Click connect, obtain this string, go inside of your database access, create a new user if needed, confirm they have the roles necessary, and confirm that you have the 0.0.0 inside of your network access. And after you do that, paste the new string here and rerun your app. and now you will be greeted to this page where you have to create your first user. So I'm going to use admindemo.com with a password of demo and confirm password of demo, and I will click create. And as you can see here, we have a fully fledged CMS here. I can go ahead and look into my users. I can go ahead and create a new user if I want to. Same goes with media. I can upload a new file. So how do we work with payload? Let's go ahead and explore some things that were added here. So I'm going to shut this down now. If you're curious about this payload app here, you can feel free to open it and click on the file inside. And the first thing you'll probably notice is a big comment here, which says this file was generated automatically by the payload. Do not modify it because it could be rewritten at any time. So yes, you are not going to touch this ever. You can look at it, but it makes no sense changing anything inside. Besides that, you have a my route, which is just a handy example of how to fetch something from payload, specifically the users collection. And now you also have the collections folder. In here, we have two familiar resources, users and media. So that's exactly what you're seeing here. You can see that this one is very simple and a very important field here. Auth is set to true. You're going to see what that means in a second. And you can probably guess what it means, right? We also have the media field here. So the best way to learn about this collections is to try and create one of your own. So if you go ahead inside of here and create categories, for example, and you can just leave it like this for now. don't do anything yet let's go ahead and try this my route so even if you don't have or accidentally deleted this my route it doesn't matter we're just going to try something so i'm going to go ahead and go to localhost 3000 slash my dash route so that's how this is how it looks like my dash route and if i click here it's just going to return a json with all of my users right admin at demo.com right so what i want to try now is the following i want to go back inside of my collections categories and i'm just going to import this and then i'm going to export con categories collection config and give it a slug of categories and then give it fields which is an array of objects a name of this field will be name and the type can be all of these things, but we're going to keep it simple with text. And let's also add a required rule. So what happens now? We just added the categories collection, right? But if you visit your admin dashboard, you are going to notice that it's not here. So how do you connect it? And besides noticing that it's not here, if you try and go inside of my route, or you can just watch me try it. If I try and load the categories collection, you can see that I'm getting an error here Categories does not exist here So what we have to do is we have to go inside of the payload And you can also quickly visit payload types here Search for categories They are nowhere to be found But the moment we go ahead and import categories from collections categories and then add them to the collections array here and save, what's going to happen now is that it's going to be generating a new import map it's going to be generating new types automatically and you can see that my route is all of a sudden no longer a problem right now you can see that i actually have categories and if you go inside of your payload types and search for categories you will see that they are now generated right but in case this did not happen for you, if you still can see it in your types, it is possible for that to happen. Don't worry, I got you. I just want to first show you that now categories should appear in your admin and you should be able to create a new category and you should be able to see them right here. So in case your payload types have not generated, this can happen if you have a lot of things running on your machine, or you might have a bit of an older machine, a bit of a slower machine, that's completely fine. What you can do here is you can manually run generating types here using payload generate types. So the way I'm going to do this is by going inside of my package JSON and adding a script generate types and just add this. There we go. Because if you want to use it standalone, right, if you just try doing this, it's not going to work. So you have to prefix it with BUNX. Or you can just add generate types and then you can do BUN run generate types like this. And this will force payload to generate the new types file. So by now, you should definitely have categories. So this is our introduction to payload, right? You can already see how powerful it is just from these few lines of code that we did. So what I want to do now is I want to copy these two things and just try and load some things inside of my UI. So I'll go inside of source app folder home page dot the SX. If you don't have a used client at the top, this means that this is a server component. So let's mark this as an asynchronous file. Let's remove all of these imports and let's replace them with config promise from payload config and payload. Make sure to not forget an add sign here. And let's go ahead and remove everything here besides a little div here. And now what we're going to do is the same thing that's happening in this route. We are first going to establish the payload using get payload and passing in a config promise. and then we're going to go ahead and load data. So this is a perfect way to load data in server components. This is the direct to database Node.js API that we've mentioned in the beginning. And now what you can do here is just do JSON stringify data null two. Basically, these two things will make it look prettier in a JSON. And now if you go to localhost 3000, if you want to, you can open two apps, localhost 3000. and let me just combine these two like this so let me wait a second for this to load and there we go and you can see that if i go ahead and add a new category for example it will add a new category it will load a new category here something new and let's click save let's wait for this to submit and the next time the user visits there we go we now have something new and we have the test category. You can see the pagination is built in into this API. So it's going to make it super easy for us to build infinite load and all the other things like that. So yeah, I'm absolutely amazed by this. And one cool thing that you can also do and try it out, which we are later going to do a bit more heavily inside of your collections, try and add an access object here. For example, create can be completely set to false so can update like this and now if you go inside of categories you can see that there's no longer the create button you can no longer see them you can see that all of these have the plus button but categories doesn't it's because we've set it to false later on we will be able to destructure the request from here And then we would check if request.user, and then we would probably add some other field like is admin, right? Something like that. So only admins will be able to create that. So that's just a pseudocode example. But that's how we're going to work with access control here. So I think that this is a pretty good introduction to Next.js, to payload. and I think it's a perfect time to end the chapter so you can digest this information and play around a bit for yourself. So let's just confirm here, we have integrated payload. Oh yeah, we didn't verify the data in the database. So yeah, if you want to, you can just go inside of your clusters here, browse collections, and you should see your dot environment here. we have added slash e-commerce at the end so you should be able to find your data inside of the e-commerce here and there we go you can see we have the categories with test and something new and users and users also have salt and hash meaning this is fully prepared for alph great so we definitely confirmed that as well now we have to push this to github so let's go ahead and do it. 04 payload integration. So what I'm going to do is git checkout dash b 04 payload integration like this. So I'm now in a new branch. I'm going to add everything. I'm going to commit 04 payload integration. You can, of course, be more descriptive here and then git push u origin 04 payload integration like this there we go so this is how my graph looks like now you can see that i'm on a new branch you can see that i'm on a new branch here so let's go ahead inside of our e-commerce here there we go you will probably get a prompt here but as always if there is no prompt you can click new pull request and click on zero for payload integration here and create a pull request. So I'm going to create that pull request. And as always, we're going to have a peek at what CodeRabbit has to say. In the previous chapter, it caught a very important bug. So I think it's going to be interesting to see what it's going to detect from all of these files right here. and here we go we have a summary by code rabbit so we have some new features and we have some chores and as you can see it gave us an extremely thorough walkthrough of all the changes we did so the changes integrate enhanced payload cms functionality throughout the project it also detected that we added the rest api and graphql handlers which is something that payload added for us. It went over each file individually and gave a summary of what happened there. And it also created the sequence diagrams for us to understand exactly how we are fetching our data. So if you remember in our home component here, right, let's go to page. You can see that we fetch the categories and display them. So it detected that and it actually created a sequence diagram for us to understand how that is happening. So I find that absolutely amazing. And it also wrote some refactor suggestions. So we know that we just did this as mock files as initial data, but you can see that it suggests a refactor to enhance this with access to add some groups here, some more fields. So I think that's very impressive. And it also found some other suggested changes throughout our code. Since we were using these files as just, you know, testing data, we don't need to resolve any of them. I've checked none of them are bugs as it was in previous pull requests. But still, I just find this so impressive. I think it's a a really nice summary for us to understand what we just did here. Great. So now what I'm going to do is merge the pull request, and I am not going to delete the branch. So now if I go here, there we go. This is the master now. So what you should do now is get checkout master or main, depending on what you use and then git pull origin master or main again let's just wait a second there we go so all of those changes here let's run git status confirm that we are on origin master as you can see i still have all the files here and you can see exactly what happened we branched out to do a payload integration and we merged it back to master if you want a sanity check you can go ahead to localhost 3000 here and you can go to slash admin just to confirm that you still have payload but this should already tell you that payload is here but there we go just go to slash admin and it will start compiling the admin perfect so let me go ahead and mark this as complete as well. Great. That's it for payload integration. Amazing, amazing job. And see you in the next chapter. In this chapter, we're going to go ahead and develop the basic search filters. So the search filters will include this input component right here and the category drop down a menu below it. And you can see that when we hover on a specific category, we're going to open a submenu rendering\nthe subcategories of a specific parent category. That being said, this entire component is more complex than it seems, primarily because we are going to develop it from scratch. So we're not going to be using any chat CN dropdown, even though it has dropdowns. I just found it that in order to make it look like this and behave the way I want it to behave, it is better for us to build it from scratch. This also includes one important functionality, which you are going to see when it comes to responsiveness of this component. Because in this view all button, you will be able to see all the categories which did not fit onto the screen, which is more challenging to develop than it sounds. But for this chapter, the goal is very simple. I just want to display all categories. I don't care about responsiveness at this point. I just want to display all categories. And when we hover on a category, I want to display its subcategories. In order to do that, we first have to enhance our categories collection, meaning that we have to add fields such as subcategories, a color, because each of our main categories will have its own background color, a slug for each category, which you can think of as a unique ID for each category, and some other fields which will help us display this in the best way possible. And then we're going to go into the actual search filters component, And I've already explained the goals there. So in order to get started, please confirm that you are on your main or master branch. Here's my graph to help you understand where we are. So we are back in the master branch after our last chapter, payload integration. As always, I'm doing git status to confirm I'm in my master branch and everything is fine. or your main branch, depending on what you use. So let's go ahead and do bon run dev and let's go inside of our source collections categories. And we are now gonna go ahead and enhance this. So the first thing I'm going to do is add another field with the name of slug, which will also be a type of text. It will also be required, but also is going to be unique. And since we are going to query by this field, we are also going to set an index fill to it as well. Now we're gonna go ahead and set the name color and the type of text. And it's not going to be required, but we will allow super admin to change the color of category if needed. And now we have to do the actual relation between a category and a subcategory. So since they are technically the same collection, what we ought to do is give each category a parent, right? Which is a type of relationship. And it has a relation to itself. And we are going to limit has many to false. And important thing is it's not going to be required. So if a category doesn't have a parent, that's how we are going to know that it's a parent category itself. And we are also going to add subcategories, which will be a type of join with a collection of categories themselves on an individual parent. So this has to refer to this right here. and this will be many and again not required you can of course uh learn more about these if you go inside of fields here let me go ahead and find the fields for example you can explore what is a relationship field right and you can uh try and you can go ahead and read this to understand it a little bit deeper if you're interested same with the join field the documentation is excellent right you can definitely learn a lot from it here i personally prefer and i always did learn by writing code so that's how i'm gonna explain this uh uh through as well so uh Let's continue with this. That's actually it for our categories collection. So if I go to localhost 3000, and if I go to my admin here, I should now see some new options. So I think I used admindemo.com and demo here. There we go. And I'm going to show you one quick trick for something unrelated, but there we go. We can see that my categories here have no slug and no color set. So basically what I got reminded of is what if you forget your password, right? Let's say I, where is my logout? I think it's here. There we go. So I log out. What if I forget my password, right? It's asking me for my email, but we entered the fake email. So what you can do in that case is, let me try and find reset migrations. There we go. So you can reset or you can do fresh, drops all entities from the database and reruns all migrations. And this will actually come in quite handy, especially if you mess something up. So add database fresh, and you can just do this like that. So then whenever you have invalid data or you just want to start again, you can just do bun run database fresh. And this will prompt you if you want to drop your database. So there we go. We just dropped our database. So if you do run dev again and go to localhost 3000 slash admin, it will behave as if you never had an account in the first place. And there we go. It prompts you to create a user. So go ahead and create the user and let's create our category now. So I'm going to go ahead and create software development now. like this with a slug software dash development and for the color well it can be anything you want but for example let's do something like this and yes you can create your own component which is an actual actual color picker but for simplicity's sake we're just going to keep it like this let me just add a little brighter color something like this so this should be like a yellow color and as you can see for the parent i currently have no options right uh i can only save this and this is now a main parent category and it should be visible on the localhost 3000 now because we do load uh them right here there we go so this is the color here and the name is software development so now what i want to do is i want to add a subcategory to it so let's go ahead uh inside of here and we can basically do it in many ways i think that we can go in here subcategories and we can click add new you can see this button here add new so you can click here and let's for example call this front end develop or just front end and just call it front end and for the color we don't actually have to put anything here because subcategories don't have to have colors the parent has to have color like this and you can just click save and there we go now you can see how my software development main category who doesn't have a parent has its subcategories. So now I am personally going to limit my categories to just two levels, right? The first level will be the parents and the second level will be the subcategories. But I am not going to allow subcategories to have their own subcategories. But you might have noticed if you click the edit button here, it actually allows me to add this even further. There are ways to validate that right so you can go ahead and go through here and search for validation of each fields so for example you can add validate here and do your own logic you can query the database in here or do whatever you want to tell the user why they can or cannot have a specific field right so i could technically add validate here and then do if a subcategory don't allow right something like that. But in my case, the only person who will be able to edit the categories is a super admin, meaning someone who developed the website, right? Someone who knows how categories work. So I don't really have to worry about some other user creating this and then having invalid data structure, right? So in my case, this is enough, right? Because I know how I will personally handle this. In your case, you might choose to not even have subcategories, right? But this is basically what I want you to have. I want you to have one main category, and then I want you to have one subcategory, which has a parent. I think that is a good start. So there we go. We now have one main category here. And if you look at subcategories here, you will find repeated documents, right? And inside of here, you can find the ID. So don't worry, we are going to display this in a nicer way. It's kind of hard to read it like this. So let's go inside of our page, inside of app home, instead of app app home page. And I just gonna do a console log of data just so we can see it in an easier way There we go So we now have documents And here's a little bit of a problem. So both of them are now loading, which is technically fine. But what I would expect is in the first level of this matrix, I only want my parent categories to load. So what I can do here is add a where. And inside of here, I can check if parent exists false. So what this is going to do is it's only going to load one document, software development. But good news is that it's also going to load the subcategories. As you can see, it loads the subcategories in its own documents here. And in here, we can find frontend. So that's what we want. In the top-level matrix, I just want my big software development group. Nothing else. So only one item in the array. And then in the subcategories, I want the frontend to be loaded. And I just want to show you one more thing. there is a thing called depth inside of the node API here. And if you set it to zero or one, you will get a different kind of data structure. So right now, everything looks the same so far. But when you take a look at the subcategories now, you can see that it did not populate the ID here. so what you have to do is you have to put depth to at least one or two let me just confirm uh there we go so depth has to be at least one if you want to load well one level deep i suppose right so zero basically means flat document whereas one means load subcategory or maybe better populate subcategories like this. So I just prefer having this explicitly rather than relying on the default. I like to see exactly what I'm populating, right? And nothing deeper than that, right? This is exactly what I need. Great. So now what we have to get started with is the actual search filters component, right? So we resolved this. And now let's go ahead and develop the search filters component. So I'm going to go ahead in here. And let's go inside of home here. So for now, we are going to stay and create the components here. But the way I want to do it is by creating a folder called search-filters. so i'm just going to create a component because it's going to have a lot of moving parts so i want to keep them all at least in one folder again don't worry we are going to separate all of these components into their own modules but we are going to do that in some other chapter for now i just want to focus on establishing an mvp with payload and the categories so inside of search filters add an index dot t s x component here let's export const search filters and inside of here let's just add search filters like this so just a very very simple component and now we are going to render that inside of the layout dot t s x so we are going to do it inside of this layout, right? Inside of app folder, app route group, home layout. Not the root layout, not this one. This layout where we render the nav bar. And just below that, add search filters. And make sure to import search filters from dot slash search filters. I'm going to close this now. And there we go. Now you should see a text which says search filters. and now we're going to go ahead and do the following so inside of this layout i'm going to go ahead and mark this as an asynchronous method and i'm going to open my page here where i have the payload imports basically i'm going to move that logic to here and if you're wondering why am i not extracting this logic and reusing it somewhere that's because this is not how we are going to fetch things in the future we are going to be using trpc but it's not the chapter to do that just yet so for now we are just using uh like a dirty quick way to get our data great so no longer need anything here we can just put home page here remove asynchronous here and remove these two imports that's it uh just make sure that your layout is asynchronous and make sure there is no use client in layout here. And now you have the data here. So we're going to go ahead and pass in the, well, we can just pass in data here as well. Let's go inside of the search filters and create an interface search filters, props or just props, data, and you can set any for now. And inside of here, json stringify data null two so basically uh you should be able to load your categories now and pass them to the search filters component so there we go we just transferred that json uh to this part right here uh obviously later on we're gonna have proper types but for now i just want to leave it like this inside of search filters let's go ahead and give the div a class name px4 on large px12 py8 border bottom flex flex column gap four and pull width and if you have an error here that's completely fine because later on it's going to look different so now your search filters components should have some more space here and now we're going to go ahead and just above this add a search input component let's go ahead and inside of the search filters folder let's create search dash input dot dsx so that component is this one right we're just going to add a simple input here before we go onto the component onto the categories so let's go ahead and create an interface props the only props this is going to have is an optional disabled prop and let's export const search input here like this and assigned props and the structure disabled and inside of here we're going to return a div with a class name flex item center gap two and with full and i want you to add a div with the class name relative and full width let me just fix the typo in the class name and then we're going to render search icon with a class name well let's just leave it like this so we so it's easier for you to understand what we're doing so just add the search input and the search icon and the input here the input from components ui input the search icon from lucid react go back to the index and import search input from dot slash search input because they are in the same folder here right so let me go ahead and refresh and there we go you should see a big input and a search icon here so what we're going to do now is the following i'm going to go ahead and give the input a class name of pl8 and the placeholder of search products and disabled of disabled. Basically, we are passing this prop from here. So now you should see a big text which says search products. But this icon is still not inside. So we're going to give the search icon a class name of absolute. left three top one and a half minus translate minus y one and a half size four and text neutral 500 there we go so this is how it's going to look like like this and let me just zoom this out to a hundred percent just to see how it's looking so the input looks a little bit too big so let me just check what it its height is uh let me try and see the height here it's 65 pixels do we have that in ram so this is height 12 how about we go inside of the input maybe it's because oh it's not p8 it's pl8 my apologies there we go so this is how the input is supposed to look like now it looks a bit more normal okay so what i'm just gonna add now is outside of here i'm gonna add to do add this will be categories view all button and to do add library button so we don't have the materials to build this at the moment so it doesn't make sense to build it because you can't even see exactly what this is going to be and it's also only going to be visible this one will only be visible on mobile and this one only when the user is logged in so those are the states we don't have yet great so that's it for the search input and now we can go ahead and focus on the categories so i'm going to go ahead and develop the categories component like this and pass in the data which in this case can just be data so let's go ahead inside of search filters folder where we have the index and the search input and create categories Let create an interface categories props and inside of here we going to go ahead and do categories any my apologies data any Let's export const categories like this. And let's return a div categories. again this can just be called props no need to be that descriptive and extract the data and now inside of here you can just import the categories from dot slash categories and you can remove the json stringification here like this so let me go ahead and try and uh do i still do the json somewhere there we go so once i refresh you should just see a text categories now one thing that i want to do with this data that i have is format it in a specific way so right now if we go ahead and console log the data i think you already know how it looks like right so let me try there we go it's a basically docs and then inside another subcategories docs right and this is great if we were to use pagination but we are not going to use pagination for our categories we are not going to have thousands of categories right it's going to be a normal amount so two things i want to do here first pagination false i wanted to always load all of them. And now the second thing I want to do is the following. I want to go ahead and do const formatted data. Basically, what we're going to do is data documents.map, and then get the individual document. And then inside of here, I'm going to spread the entire document and then get the subcategories and do the same thing. So doc, subcategories, docs, like this. Now, the problem is this is optional so what we ought to do is this like this dot map and then just get the dock and spread uh the dock i think this is how i imagined it let me just see yes you will see an issue at this point and i'm going to explain what's going on here but i think that i did this correctly so now let's have data and let's have formatted data here so you can see the difference between what we originally fetch and what i transform it into here if i did this correctly let's see so we now have data which is our good old docs and then again subcategories docs basically a very long json whereas our formatted data is just our main category right software development and then subcategories immediately the object right and then finally nothing here uh great so why is this happening why is this warning me of an error uh here is why remember when i explained depth to you right so if i say depth is zero in this case subcategories documents, every document would be a string, right? Because it would not populate them. But if I change this to one, in that case, it becomes the category object. At the moment, payload SDK cannot automatically infer the type based on depth. But then again, depending on when you're watching this video that might not be the case because they do have a pull request which features the depth generic and fully type safe result types for relations and joins so that is this exact thing that we are talking about if you specify a depth it is normal to expect the type to be the proper object, right? But in our case, we have several options here. Since we know that by adding depth one, right, we've added a console log, we've convinced ourselves with our own eyes that in that case, the document is populated, there are a couple of solutions. So you could do just doc, maybe like this, as category from payload types. All right, so let me just import this and then we'll see what we can do. So you can import category from at slash payload types. So maybe not like this. Let's see, maybe like this. There we go. So if you do it like this, all of your problems are solved, right? And you can add a comment here explaining because of depth one, we are confident doc will be a type of category. Right. So I think this is a good comment to have just for you to understand why you need this. Right. Because they don't automatically infer this. so populate subcategories subcategories zero will be a type of category there we go so you can just add this little comment if it helps you understand what's going on whereas if you change this to zero i'm pretty confident the entire app at this point will uh maybe break let's see formatted data uh there we go yeah you can see what happens the subcategories have basically split the entire string id uh to that but if you add depth one just go ahead and confirm this yourself right if you add depth one you should see subcategories as completely normal objects here so that's quite important for you to have and if you want to you can also do subcategories here as undefined maybe just so you don't have an additional array here let me check if that is correct so i have one subcategories and there we go now in here front end doesn't have its own subcategories so now our formatted data is exactly how i want it to be so we can take that and we can pass that to the search filters uh we don't have to change this because we already set it to any and now we can go ahead and render that here so json stringify data null two and this is now officially the data we can work with there we go so we just get an array of our main category and then its subcategories here featuring the name frontend there we go so it took us half an hour but we finally got to this point where we can build this component So now what I want to do is I want to go ahead inside of this div and just go over categories and biopologist data dot map and then get the individual category. Like this. And inside of here, I'm going to add a div. With a key of category dot ID. and if you want you can do category category from payload types there we go so this is kind of a dirty way to have the types again don't worry later we're going to use trpc so all of our types will be inferred throughout the app don't worry this is not going to use any annies or tricks like this it's going to have proper types uh so inside of here let's add category drop down and let's pass in the category to be category like this is active for now will be false is navigation hovered will be a prop that will be useful later but for now we can just set it to false so instead of search filters now add category dash drop down dot t s x let me show how that folder looks like now so search filters has categories category drop down index and search input let's go inside of the category drop down here so interface props it will have category which can be a type of category here is active which can be an optional boolean is navigation hovered an optional boolean let's export ones category drop down we can go ahead and destructure the props category is active is navigation hovered and now what we're going to do here is finally render a button so let's go ahead and import a button component here and for now let's just do category.name in here make sure you have imported components ui button here go back inside of your categories and import the category drop down from dot slash category drop down so now when i refresh this I should be seeing a component here. So let me just check why is it not working. Category dropdown. Let's see. Console log data. Test. Just to confirm, is it loading here? Data test. It is. it most certainly is loading here. So data.map, category dropdown, return. What if I add variant elevated? Maybe it's that. Okay. Let's see. Maybe I'm missing something obvious and you have already seen it. i don't know uh am i rendering everything else correctly index categories i am rendering the categories we have this here category drop down okay i'm just gonna pause and see what i'm doing incorrectly all right uh i not rendering this correctly i using curly brackets So that not causing a render My apologies So now when I refresh, there we go. You can see a big button and software development. So let me just show you. So just a category dropdown is being rendered here. And inside of here, I added this console log while I was debugging. So I noticed that this never renders. And then I figured that, oh, it must be because, well, I never render it. So I'm just going to remove this console log and I added this variant elevated here. Everything else is exactly the same. Nothing has changed. So yeah, if you have the same mistake as I do, just fix using the proper brackets and now you should see the text software development here. So let's go inside of the category dropdown and let's go ahead and style this even further. So it looks a bit better. I'm now going to add a class name here. With the CN util, you can import that from lib utils like this. And inside of here, add a height 11, px4, background transparent, border transparent, rounded pool, cover, background white, cover, border, primary, text black. Like this. so only when you hover will you actually see the effect happening like this and then i'm just going to add a couple of things so if is active and if not is navigation hovered so this is some additional logic which will come in handy later in that case just add background white and border primary. So basically what this will mean, if I click on software development and software development becomes the active category, we are just going to add these two additional styles to ensure that it has the same style as if it were hovered, right? So that's what we are going to be doing. Right now, it doesn't make sense because we can't have active categories. Great. So what we have to do now is that when I hover over it, it needs to drop down and display the subcategories. And that's going to be the last thing we're going to do for this chapter because it's already long. But I do think we can do that here, even though it will take some time. So the first thing I want to do is I want to add a state here. Const is open and set is open. Whoa, my apologies, I didn't see this. Const is open and set is open. Use state set to false. And you can import this from React and mark this as use client. Otherwise, you're going to get an error. So just make sure you have use client in your category dropdown. Then add a dropdown ref from user ref, give it a type of HTML div element and null. Make sure you have imported user ref from React. All right, and now do const on mouse enter. If the current category has subcategories, in that case, set is open will be set to true. So only we actually have something to show are we going to open the actual subcategory menu. And do const on the mouse leave set is open to false. Like this. So now let's go ahead and wrap this button in a div like this. and let's give this a class name of relative, a ref of drop-down ref, on mouse enter, on mouse enter, and on mouse leave, on mouse leave, like this. So nothing much should change now, but the state is open should change depending on if we are hovering over this or not. So we need to have it in JavaScript because it's just not enough for us to have it using the hover methods. So now I'm going to wrap this button again in a div. And I'm going to go ahead and give this a class name of relative like this. So just that. And then what I'm going to do is I'm going to go outside of this button, but still inside of this class name relative. and I'm going to check if category subcategories. So if we have subcategories and if category subcategories length is larger than zero, in that case, we're going to add a div here, which will actually be a self-closing div. And we're going to give it a class name, which is going to be dynamic using our CN util. And we're going to do the following. opacity 0 by default absolute minus bottom 3 width 0 height 0 order l 10 pixels if you're wondering what this will be also you can ignore this error for now what this is going to be is this this little pointer right so that's what we're going to do now so you can see something happening when you hover over this. So opacity 0 absolute minus bottom 3 width 0 height 0 border L 10 pixels border R 10 pixels border L transparent border R transparent border bottom black left one and a half minus translate minus x one and a half and then add a dynamic class if is open in that case opacity 100 so let me just refresh and see I can't see it appearing so I am going to check if everything I did here is correct starting with this hello so I just want to ensure that this happens let me just see something weird is happening now let me try expanding a bit So it properly enters here. Okay, that is correct. Let's see. Categories, subcategories, and if category, subcategories length is larger than zero, then we render this. Let me just confirm that I did this correctly. I think I forgot to add one thing. border bottom and pixels as well so border left border right and border bottom and there we go okay now you can see it's starting to appear here so it's not perfect it's a little bit glitchy but it is it is appearing every time that the when i hover on something great so let's just confirm we have this set relative we have that set the relative uh and i think that this should be okay for now because we are missing another component that we need to have here which will finally make this look like it is supposed to uh so in order to do that we first have to develop a use drop down position hook so basically in order to make this work properly we can't just show a drop down below this because it won't exactly work on some smaller devices and it can be quite glitchy if you're not careful so we have to develop a hook which will help us determine the best position for this drop down to open into so let's go ahead and let's go inside of well we can do it inside of search filters just create a hook called use dash drop down dash position dot ts so use dash drop down dash position and inside of use drop down position export const use drop down position here inside of here we're going to add a ref which will be ref object from react HTML div element or null or like this or it will be a ref object of HTML div element whoops like this and now we're going to develop a method get the drop down position like this and that will be the only thing we're going to return from this hook get dropdown position. So you can go back inside of the category dropdown here, and we are just going to prepare that. So here where you define the dropdown ref, you can just do use dropdown position and pass in the dropdown ref. You shouldn't get any errors here because we added the correct type. And now from here, you can extract get dropdown position. And now we are going to use that drop down position to correctly render the submenu. So let's just go ahead and develop this here. So we're going to start by checking if anything is missing. So if we don't have ref done current, we're just going to return the default top and left to be 0, 0, right? There's nothing for us to calculate here. Now let's go ahead and get the rect. using ref current get bounding client rect. Let's define the dropdown width. So this is how wide the dropdown will be, 240 pixels, like this.\nuh and the way we calculate this is basically width of drop down it's going to use this class name w60 which is equal to 15 rem which is equal to 240 pixels right so that's how we get to this so we are going to be using uh this class name on the subcategory menu later on right it's just in case you were wondering where did i get this magic number so let's go ahead and calculate the initial position so let left is going to be rect.left plus window.scroll x const top will be rect bottom plus window scroll y now we're going to check if drop down would go off the right edge of the viewport basically we want to make sure that our drop down menu behaves correctly in all cases so if left plus drop down width is larger than window inner width that means it's overflowing so we're going to align to right edge of button instead so this is something that most of this drop down libraries have built in but in our case we are building our own here so let's do the left is now rect.right plus window scroll x minus drop down width we are basically preventing things from overflowing so if after this we are still off screen aligned to the right edge of viewport with some padding so if left is smaller than zero in that case left will be window inner width minus drop down width and padding which is 16 like a buffer here and we are close to finishing now so one more check ensure drop down doesn't go up left edge if left is smaller than zero left is 16 basically some padding and return top and left there we go so let's go over this again so what's happening here if the drop down ref which we passed right let's see so this will be the drop down the ref our div wrapping our button that's the drop down ref that's what we are passing here so if we can't detect that element we are not even going to bother trying to put the subcategory menu in a perfect position we're just going to return the default top left zero right so we are going to be using uh the get bounding client rectangle uh from ref current we are defining the drop down width because we obviously use it in a lot of calculation. We know it's going to be 240 pixels wide because, well, I told you that. And I'm going to come back to this when we develop it in a second. And then we are basically calculating the initial position of the dropdown menu. And we are just making sure that it doesn't overflow, right? We are making sure that no matter how user behaves, whether they do this or that, we're making sure they can always, it will basically move. it will be maybe here maybe it will be here depending on how the user is looking at it at any given moment right so that's what we're handling here all right so i think we've developed this correctly and now what we can do is we can go ahead outside of this div here and add subcategory menu and pass in the category category is open is open and position drop down position and drop down position will come from get the drop down position and you can we can call that here and this will be dropdown position. There we go. So now a subcategory is not defined, of course, so we do have to define it. So let's quickly do that. Instead of search filters, add subcategory menu.tsx. And I'm gonna go ahead and create an interface for this, but change this to be any. or you can just do category from payload types. So subcategory menu props like this, it will have category is open and position. I will just change this to props. Let's export const subcategory menu. Let's go ahead and use the props. And let's extract them. So category is open and position. And now inside of here, what we are going to do is check if we are not open or if there are no category subcategories or if we do have category subcategories, but its length is zero. In that case, we are going to return null. Right. So we are only going to open the submenu if all of this match here. and let me just check this so should i do it like this like that it's not satisfied no matter what i do i'm just going to leave it like this for now and we're going to come back to it later because we know it works we know that subcategories is an array oh yeah it's because we we use formatted data right so it doesn't know it's expecting us to access the docs first right but we know that's not how it works now so yeah you can leave it like this for now later when we have type script is going to work differently so now what we're going to do is const background color will be category dot color or we're going to use F5, F5, F5 as our default color. And now inside of here, we can return a div and a class name of fix and the Z100. And we can give it a style, top, position, top and left, position, left. and now inside of here we can add an invisible bridge to maintain hover if you're wondering what that is if you were to hover over business and money this drop down menu would open and then if you were to hover in between these two lines it would close and that's pretty annoying so what i figured we would do is create like an invisible bridge here which will simply maintain that element's height right so that's what we're doing so let's go ahead and create this invisible bridge here so it's a self-closing div with a very simple class name height 3 and width 60 so this is what i was talking about here that's going to be the width of the drop down of the subcategory menu with 60 so now we can use the another div here with the class name with 60 text black rounded medium overflow hidden and border like this and inside of here what we can do for now is just say a paragraph subcategory menu so let's see what we have for now so all of these errors are completely fine we're going to uh resolve that later so now import subcategory menu and render it here and let's refresh let's see if we have any errors uh there we go so you can see my subcategory is appearing here but it looks like this little button is still stubborn so i'm gonna see uh how to resolve that as well but let's just continue with the subcategory menu for now so what I want to do is I want to go back here in this div with 60 text black rounded medium overflow hidden border and let's give it a shadow of four pixels underscore four pixels zero pixels underscore zero pixels and then rgba 0.0.01 like this and then minus oh make sure you add another so let me show you how this is supposed to look like in one line you can pause the screen and take a look all right so make sure you close the square bracket and then minus translate minus x two pixels and minus translate minus y two pixels so let me just refresh there we go so now you can see it's starting to take some shape here and now we're going to add the background color so i'm going to add style background color and it's basically going to be this constant so it's going to read the color which we defined from the admin dashboard right it's looking pretty cool and now what we're going to do don't worry no more components what we're going to do now is add a div here like this and category subcategories map subcategory you can give it a type of category i think it's just going to make this a little bit easier and add a link from next link give it a key of subcategory.slug and in here render subcategory.name and you can give this an href for now just an empty string Later we gonna give it a proper href when I explain to you our routing logic And add a class name here Full width, text left, padding 4, on hover, background color black, hover, text white, flex, justify between, items center, underline, font medium. Like this. So now when you hover, there we go. You should see the only subcategory in here like this. But there are a couple of more things that we have to do here. But this was basically what I wanted us to achieve. I wanted us to load the main category and the way to load the subcategory. So I just want to find a way to properly position this caret before we go on. So I'm going to go back inside my categories here and see if there's something I have to do here. So I'm just going to add the proper wrapping elements. For this outer div in my categories component, I'm going to give it a relative and a width of full. I'm then going to go ahead and wrap this into another div. and I'm going to give this div a class name, flex, flex, no wrap, and items center. And there we go. That resolved it. So I think it was this class name around each category dropdown. We basically needed a flex, flex, no wrap, and items center. There we go. So you can see that now software development has a very nice front-end submenu. So now if you go to your admin, so localhost3000 slash admin, go inside of categories. Let's try adding a subcategory. So I'm going to go inside of my front-end and I will add new. Oh, no, this is the incorrect one. My software development here and click add new. And I'm going to add backend now. backend and let me just find a nice little actually no backend doesn't need the color my apologies so I'm going to save that maybe add a new one mobile mobile right so this subcategories they don't need colors they just need a parent so now when I refresh here there we go you can see how this looks like mobile backend frontend right and if I go ahead and go inside of my categories and create a completely new parent category. For example, let's call this design. Let's call it design. Maybe design and UX, something like that. And let's add some fancy color here like this. And there will be no parent. So you can just save it like this. So now, there we go. You can see that design and UX is a new component here. but since it has no subcategories, the menu never opens, right? We only open the menu if we see that it has subcategories, right? So let's go ahead and just confirm that it's working by adding a new subcategory here. I don't know, let's call this user experience, user-experience like this and save this and let's add a new one called user interface. user-interface and click save so basically just adding something and there we go so this is what we were trying to achieve and it looks pretty good so uh what we want to do next is we want to implement the proper way for them to render responsibly because right now well i don't think you can actually see it because it's only two of them, right? But you can see this is not exactly usable on mobile, right? You can't exactly hover properly here. So we will handle proper mobile mode so that it can be accessed by mobile as well. Great, so I think that marks the end of this chapter here. This was what we wanted to achieve and we did it. So let's mark this with this. And now we have to push to GitHub, of course. So this is called 05 search filters. So we do have some errors here. So our code rabbit will definitely tell us some advice, but I'm going to leave it like this for now. So 05 search filters. So let me shut this down. Git checkout be 05 search filters. Git add, git commit 05 search filters. Git push U origin 05 search filters. There we go. As you can see, I am now in my branch 05 search filters, and I have separated from the master or main. And now we're going to go ahead instead of GitHub. And there we go. I'm going to open a new pull request, make sure the base is main or master, and compare is 05 search filters. There we go. I'm going to click create pull request. And we're going to see what our reviewer says about our code. And here we go. Summary by CodeRabbit. So this pull request introduces a new database migration script in package JSON and updates the asynchronous data retrieval in the layout component to fetch and format category data. That's exactly what we did. So changes file by file. We added a new script to reset our database. We converted the layout to an asynchronous function so we can get our categories. We have changed home from an asynchronous data fetching component to a normal static display. It then describes how we created the search filters component, including the categories, category dropdown and everything else. And in here, you can see the sequence diagram for fetching the categories in the layout. But here's an interesting thing. you can also see the sequence diagram of how our category dropdown and use dropdown position works. So when user mouse enters on category dropdown, it will calculate the dropdown position using the use dropdown hook and it will return set position and then it will render the subcategory menu. And on exit, it simply closes the dropdown. And inside of here, it left some comments, which I already guessed that it's going to talk about, which is my any types uh absolutely agree and we are not going to be using the type any it is just temporary until we add trpc and it also added this is quite interesting so it understands the correct structure but it did not catch that we formatted uh this into uh our without docs right we removed the pagination element from it. But still very interesting that it is aware of how it looks directly from the API. So no need to change any of this, of course. Same thing here, right? It's just mentioning these things. It noticed an invalid Ahrefs, which is something we're going to add later. But this is an interesting one, which I've noticed. Oh, yeah, this is cool. So improved touch device support. The dropdown currently only appears on hover, which doesn't work well on touch devices. So that's interesting. We could refactor this. So on click, toggle dropdown, right, instead of just on mouse center. That's very interesting. We could do that as well. Yeah, it wouldn't harm us if we were to add on click to open the dropdown as well. And here's another interesting one. So it enhanced accessibility for dropdown menus. So if we use the enter key, right, or escape key to close it, and it added the area attributes here. So very useful for us, right? So at the moment, I'm going to stay true to my source code, but these are some very, very useful suggestions. Very interesting. It also tells me that I left a console log and some other recommendations here. Very, very cool. Great. Let's merge this pull request. I am satisfied with how it is at the moment because a lot of it is just temporary. I'm not going to delete the branch. So now instead of here, I should have 05 search filters. And now what we ought to do is check out back to master or main. Here's a little tip. You can use git checkout and then just an empty dash. And that will bring you back to whatever branch you were previously on. in case you didn't know that. Otherwise, just use this or, of course, main, if that's what you use. And now let's do git pool origin master or git pool origin main. And git status to double check. And there we go. Everything is up to date. And let's just check the graph. There we go. So we detached into a new branch, 05 search filters. There we go. and then we merge that back inside of master. Excellent, great, great job. Let's just go ahead and mark this as complete as well. Amazing, amazing job. In this chapter, our goal is to continue where we left off with the categories and try and wrap them up. So that will include finalizing all the small details that we have around the dropdowns here. And that also includes the dynamic hiding and showing of categories depending on how wide the user's screen is. That will also include the view all button, which is then going to open the sidebar where user can view all categories which did not fit on the screen. In order to make that a little bit easier to set up, I figured we should create a seed script for our categories. And so that you can always, you know, just database fresh your database, right? So it starts from scratch and then you have an easy way to just bring your categories back because you will most likely want to have the same categories correct So this is where kind of using Bun will be crucial because this is a script that we going to run as a standalone script So it's much easier to do that with Bun. I explained why in the beginning, right, in my setup chapter here, zero config script running. So it supports out-of-the-box TypeScript and ES6. Nevertheless, if you for any reason have problems running BUN and you fail to run the seed script, that's okay. It's not going to block you with continuing with the tutorial, right? So if you, for whatever reason, fail at seeding the categories, don't worry at all. So let's go ahead and first confirm that we are on our main or master branch. So just make sure you have merged all the changes from the previous chapter. As always, I'm going to do git status to see that I am properly aligned. Now, what I'm going to do is I'm going to go inside of my source folder here, and I will simply create a seed.ts file like this. And now what I have to do here is I have to create a constant categories like this. And inside, I have to fill all the categories that I want. So this can be the simplest one, like all with a slug of all, for example. I mean, technically, I don't think you would really put all as a category, but you know, if you want to do it, you can. So I have prepared for you a public guest, which you can visit. You can find the link in the description. or if you have access to the source code you can just go inside of the seed file and then you will find the categories here right because it doesn't make sense for me to write all of this so i'm just going to copy this entire categories file and i'm just going to paste that here and i'm actually going to remove the all right i don't i don't like having that okay we can leave it for now actually but later i will think of a better solution to display all categories great so now we have an unused categories constant as you can see we have some main categories like business and money and then we have subcategories so that's exactly what we want to do and each main category also has a unique color so we can differentiate between each category so now what i'm going to do is I'm going to import get payload from payload and config from at payload dash config. And now I'm going to go ahead and create an actual seed script. So const seed will be an asynchronous method. Let's define the payload using await, await get payload with the config like that. and now what we have to do is we have to go over each category and create a subcategory if it exists so for const category of categories const parent category will be await payload create collection will be categories and data here will have a name of category dot name it will have a slug of category dot slug color of category dot color and it will have a parent of null because these are parent categories and now what we're going to do is for const subcategory of category dot subcategories or an empty array if there's none and then we're going to go await payload create collection categories again with the data of name subcategory.name slug subcategory.slug and parent parent category id there we go so just like that we've created a nice little seed script here and now what we're going to do is just await seed and process exit zero like this so that's my seed script all that's important is that you define your categories here so this is the format right for main categories they need to have a name and slug and then you put subcategories inside of an array like this and they just have name and slug and you can also add a color to the main categories you don't have to but you can all right so we have this seed And now what we have to do is we have to add it to our package JSON here, right? So I'm going to go ahead and add database seed. And I'm going to do bun run source seed.ds, like that. Or you can run this directly inside of here, like this. So what I'm going to do first is I'm going to do db fresh. so basically i want my entire database to be cleared so i will get prompted now there we go i say yes this error is fine because we have no migration files because we are using mongodb and now let's do bun run database seed like this so if no errors appear i think we've done everything correctly there we go no errors have appeared and it was successfully shut down after that uh perhaps i should have added this in try cache maybe i don't know let's see uh bon run dev if yours didn't work it could be because like this part maybe or maybe it's fine i don't know mine seems to be just fine as you can see i have all categories here uh but you can see that we are running into some issues even our drop down position here gets confused that's because this part is not supposed to be showing right so that's what we're going to be working on we're going to be working on ensuring that it only loads up to here right because everything else you can see gets cut out so it's just weird right so our calculation script works great but maybe even this part shouldn't really show because you can see it's kind of cut off in this corner right so we are going to solve that by loading categories up to here and then we are simply going to add an additional last button here called view all and that view all button is going to open a sidebar showcasing the user every single category they can have uh great and if you want to improve your seed script, I think you can do this, like try await seed and then console log seeding completed successfully and then exit with zero. And if you catch an error, log that error and exit with error code. I think this can kind of improve it. And I'm going to add one more script here, which I will be using a lot. Database reset. So this will not be any payload reset script. It's simply going to do bun run database fresh, and then it's going to do bun run database seed. Now, I want to point out something important about our database commands here. In the previous segment, we created this command, database reset, which simultaneously calls the database fresh. and immediately after that, it calls database seed. And while this might seem convenient, it can actually cause issues. And I no longer recommend this approach. The reason I don't recommend it is because I'm actually recording this in my chapter 15 of this tutorial. I ran this script right here, and this is what happened. As you can see, I started getting these errors for my categories, where it says that it has a write conflict, a transient transaction error. And initially, I did not get these errors. But it looks like when I increased my seed script, which is something we're going to do in later chapters, I started getting these errors, write conflict. Now, write conflict by default means that we are trying to create something that already exists in the database in regards to a unique field. And then I realized when we run database seed, there hasn't been enough time passed in between these two scripts, right? So basically, this happens because we are trying to seed the database immediately after dropping and recreating all the tables. But MongoDB needs a moment to settle after the migration. And our end-end operator jumps straight into seeding. So instead, I no longer recommend using this at all. Instead, just use these independently. You can see what happened here. So I ran bun run database reset and I got an error here. It said that my categories are conflicting. After that, I went ahead and removed the categories from my script because I thought that the categories were problematic. But then you can see that in here, it's actually telling me something else, right? You don't even know what this is. This is in chapter 15. This is my tenant. And then I realized, oh, so the script must not be working. And then I first tried manually cleaning the database and then running database seed. And would you look at that seeding completed successfully. And then I brought back my categories and I tried the same thing again. And again, it succeeded. it so just to wrap it up i no longer recommend using this at all go ahead and remove it and every time you see me using it use this wait a few seconds and then use this instead seed as simple as that so then whenever i want to i can just do bond run database reset and this will first clear the database right So I have to say yes And then immediately it runs the seed script And now it should add a console log. There we go, seeding completed successfully. So I think that's a cool way so that you can always quickly reset your entire database, but keep the categories intact because you are almost always going to need them to try anything, right? So I'm going to refresh this, just confirm that everything here is okay. And now we're going to go back inside of the category dropdown component. Everything is fine, great. So this is what I don't like. I don't like that when I hover over this and then I go down, it disappears from its hover state. So let's go ahead and resolve that. Let's go inside of category dropdown component. So category dropdown component is inside of search dash filters here, category dropdown. And this is where we can do that. So find the button with the variant elevated, which renders the category name. And what we are going to do is we're going to add if is open, in that case, add background white and border primary like this. So now you can see that it doesn't lose the selected state. Now, depending on how you want it to behave, you can also keep its elevated state, right? You can see that it loses the elevated state but at least now the border stays right previously not even the border states it just looked weird right because it disappears so what you can do is go inside of the button find the elevated hover and then just copy this shadow and translate let me just copy the entire thing you don't need the transition so just copy this shadow and you can just add all of that here and just remove the hover prefix on these three items. If you want to, you don't have to. And now you can see how it stays elevated while the sub menu is open. If you prefer it that way, you can do it that way. So that was really bugging me. I really wanted to fix that. Great. So the second thing I think I put inside of our itinerary here, all right, we see the categories, is to resolve the TypeScript issues, at least temporarily. I just think this is very annoying to have these errors. So what I figured we should do is go inside of home and create types.ds. Again, we are going to organize this much, much better than what it is at the moment, but not the time for it right now. So export and let's call it custom, export type custom category, maybe like that and this will be category from payload types and then subcategories which will be another type of category but an array right i think if i hover over let's see how does that look like i think this should be fine uh let's go ahead and try using that somewhere so inside of my search inside of my search filters index I accept this let's say can I do custom category and then an array of those basically so if I see where I render my search filters looks like it is working so what I can do is I can also set the formatted data to have the custom category and an array of that and i think that now if i if i gave it something random like test a yeah then it throws an error so it looks like our custom category uh was correct in this case so make sure you import custom category from dot slash types this is how it's supposed to look like and then simply add it as a type here for the formatted data and you should have no errors with this right that's because subcategories can be undefined. And we make sure that we map this as category. If you remove this, I think everything will start throwing errors. Okay. And let me just see. So now this is okay. So no errors here, but we have to do the same thing for the categories here. So custom category and an array from dot dot slash types. I'm going to remove the console log. And it appears we are no longer getting the errors, but we are getting it here. So inside of the category dropdown, change this to custom category from dot, dot, slash types. And we're now going to start seeing, okay, so category. No, we don't have to map it here. There we go. So remove that type here and you can remove this import. So that part is solved. And now we have the custom category here. Let's see what the error is here. So we don't log in this. There we go. I think that solved all type errors because it looks like subcategories. Why does it think that it still has docs? Right, because that's not our type exactly. Category, what's our category type? So category and subcategory. But what we have to do here, I think, is category, maybe omit, like this. Let me check if that will maybe improve it. Okay, I'm not sure, but I'm not getting any errors. So I'm just going to leave it like this for now. or maybe can it be like self-inferring what does that do okay now that causes problems okay yeah i'm not gonna do anything more okay so i will just go back here instead of my custom types and i'm just going to bring this to category that's it i'm not getting any errors if you are it's fine it's completely fine because we are going to use typescript later on so we're just going to infer whatever the server gives us we're not going to have to do all this specific types we are not going to be fetching like this in the layout we're going to have a much nicer data access layer later on but for now i just wanted to get rid of the errors in the search filters and in the categories so that we can focus you know on developing inside of this area so the goal for now is to make this responsive so hide categories which do not fit at the moment that's not happening i can scroll all the way here and i can see that i have some categories which clearly do not fit here so let's go ahead and do that we are going to have to do some things here so inside of categories let's do const container ref to be use ref from react and let's give it a type of html div element and null as default we can copy and paste this and call this one measure ref then below that view all ref like this then we're gonna add some states here visible count set visible count use state here and this will be data.length like this then in here let's do is any hovered set is any hovered use state make sure you import this from react false by default and then we're going to have is sidebar open and set is sidebar open like this okay and now what i want to do is add a use effect here make sure you import this from react so use effect there we go uh and i will just do one thing here so i i'm at the moment just going to mock the active category to be all like this so i will just assume uh this category like this and let's also do one more thing oh yeah we have to mark the categories as use client use client okay so now we should no longer have any errors let's just order them correctly i want all to be in the first place and at the moment one way we can do that is find where we load them and we can add i think an order uh direction let me just check how it's done it's sort and we can just put name because all will be first that way later on i'm thinking of just hard coding the category called all right but at the moment it can stay like this okay so make sure you add a sort name just so the category all is first right it just makes more sense that way uh great so now we have to fix this problem right here and since we have the active category all uh what we can do is we can go here and set is active to be active category category.slug and there we go now all will appear as the active category as you can see it's kind of circled right the border of it is active uh okay so now let's go inside of here and let's complete our use effect but we just need to add a few more things so const active category index will be Categories, oops, data.findIndexCategory, category.slug equals activeCategory. What am I doing now? What I'm doing is I'm creating a logic which will tell me, is the current activeCategory,\nmaybe this one, right? Because if our current active category is a category that we have hidden away, a category which will be only visible once user clicks on view all, what I'm going to do is I'm going to highlight view all as the active category. So the user knows none of these categories are the active one. The category that you are in is too far out to be visible. so I'm going to give you a visual hint where you can find it right because it doesn't make sense that not a single category is selected when a user is obviously in a category so is active category hidden will be if active category index is larger or equal than visible count and if active category index is not minus one meaning that it's not in part of the array at all so now inside of the use effect let's do const calculate visible so we are calculating visible categories here if we are missing the container ref or if we are missing the measure ref also make sure you add dot current in all of these places like this or if we are missing view all ref current in any of these cases we can break this function because there's no way for us to calculate anything here so now let's go ahead and add some constants here container width will be container ref dot current dot offset width then we're going to do view all with this view all is referring to the button which we're going to add later oops so we're going to ensure that the view all button is always visible and then available with will be container with minus view all with so we are ensuring that view all always has its space right we don't ever want that button to be hidden now let's go ahead and do const items array from measure ref dot current dot children now if you're wondering what measure ref is it's quite interesting actually you're gonna see in a moment what it is it's a very unique solution to this problem so let's now do let total width to be zero let visible to be zero and then for const item of items which we get from this measure ref let's get the width of the item using item get bounding client rect dot width if total width plus the width of that item does not exceed available width we're just going to break the function otherwise the total width will be increased by the width of that item and we are going to increase our visible count and then let's do set visible count to visible like this and now what we have to do is we have to add a resize observer so outside of that function uh calculate visible make sure you're doing it outside but still inside of the use effect const resize observer new resize observer calculate visible and then resize observer dot observe container ref dot current so that's what we're going to be looking at and you can put an exclamation point here at the end because we know at this point it's going to exist and very important on unmount make sure to disconnect the resize observer so we avoid any overflow and inside of here uh let's see do i need to add anything um well i i think i need to add the length of the categories my apologies data.length here i think i need to do that so that it changes every time we get a new category. If that happens, that's not supposed to happen. But if it does, it's going to recalculate everything. Okay, so now we have that. And now we need to create this measure ref, right? If you're wondering what that is, measure ref will be almost an identical copy of this. So just copy it. but what we're going to do is we're going to hide it we're just going to use it to measure how long this will actually be so let's go ahead and do the following here ref will be measure ref like this and you can add a little comment here so you understand hidden div to measure all items so we want to do that ahead of time right measure ref class name here will be absolute position, my apologies, opacity zero, pointer events none, and flex. So we don't want the user to see this. And the style here will include position fixed, top minus 9999 and left minus 9999. So we need to hide it like this. We can't use display none or something because that will mess up the calculation. And I think this can stay exactly the same. I think this is just fine. And now in here, we can set visible items, right? So you know that these are the ones the user is actually going to see, but these are the ones we are using for our calculation. We just need them to have the same amount. But something will be different for this visible items now. So let's go ahead and add some refs here so this div is our container ref like that we already have the class name set now what i want to do is that on mouse enter here to be set is any hovered and set it to true and on mouse exit it's going to be false uh on mouse leave my apologies like that and then we're not just going to iterate over entire data instead we're going to slice by the amount of visible count starting from zero right and then we can also do here is any hovered like this. And what we have to do now is we also have to add our view all button. And we are actually going to do that inside of the container ref. So make sure you do it like here. This div will have a ref of view all ref and the class name of shrink zero. and then inside of here we're gonna add a button make sure you import the button from components ui button and this button will basically be a copy of what's inside of the category drop down here so it's going to be this button here so let me go ahead and just copy uh how about up to this part we don't need the is open part because view all button this one which we are developing will not have any of those so class name here cn and like this so now i'm just going to go ahead and improve this so let me just collapse this like this okay so instead of is active this will be is active category hidden and is any hovered here uh the rest i think can stay the same and we have to import cn from libutil so yeah height 11 px4 background transparent border transparent rounded full hover background white border primary text black and then if any active category is hidden we're just going to highlight this category as the active one right because this is the view all button and we're also going to add it a list filter icon from lucid react and give it a class name of ml2 so make sure you have imported this and cn from lib utils and i think that already we might actually be ready to try this out the only unused things are this the sidebar open because we don't have it yet uh and you can see yeah for a second it kind of loads them there but the moment you know it's over you can see how uh it doesn't anymore. So there we go. You can see how this works. It calculates, you know, based on the amount of space we have, even though, you know, you don't really have to always assume that user will be doing this, right? This is a very, very edge case. I think that, you know, some original websites where I was looking at for this dynamic navigation menu don't even fix it if user manually changes things, right? They only account for the initial load. And I think this is a very, very cool result. So I know that we spent a lot of times building this, but you know, that's kind of the point of my tutorials. I like to focus on details like this. I want to make it exactly as I've seen in these other websites. And I want to explore which possible ways did they do it, right it would be easier for me to just give you the simplest possible drop downs here but it wouldn't be as nice it wouldn't be as cool now that we have this what we have to do is we have to create the sidebar which will open up and display all the categories which were hidden right so that's what we're going to be doing with this so let's start by finding the button here the view all button and on click what it's going to do is set this to true like this and when this is true what we're going to be able to do is render the categories sidebar so i'm going to render the category sidebar right here in the beginning categories sidebar actually no need for a comment here it's going to be very descriptive itself so categories is sidebar and passing the open is sidebar open and passing the on open change to be set is sidebar open obviously this does not exist yet so right now it just an error so let go ahead now and we can do it inside of search filters Let go ahead and create the categories sidebar like this. Let's export const categories sidebar and let's return a div categories sidebar. Now let's go back inside of categories and we can import the categories sidebar. We are now only getting the type errors. Just confirm that you have the import here. So now I'm going to go ahead and create a interface props here to accept a open Boolean and an open change, which accepts the open Boolean like this. Let's make sure to assign these props. And by now we should get rid of the errors in the other file. There we go. So now what I want to do here, okay, this is how we're going to do it now. I'm going to accept my categories here in a form of data. So what I'm going to do here is I'm just going to put the custom category here like this. Later, that's not how it's going to work. you're gonna see why because we are going to be using this category sidebar in some different places so it will be better for category sidebar to independently fetch the categories which is not something that it can do now because it's a client component and we haven't really set up any you know client side fetchers so it's easier for me to just demonstrate like this now to do remove this later so let's go ahead and do this now we have the data we have all the categories we need here so what we're going to do is the following let's import everything we need from the sheet component here just so we can see it actually open like this let's also add everything we need from the scroll area from components ui scroll area and now let's go ahead and actually turn this into a proper component so sheet with an open on open change now let's add the content here let's give some props here side will be left class name will be padding zero and transition whoops none and style here will be background color for now white but later it's going to be dynamic now let's add the header and let's give the header a class name of padding for and border bottom let's add a actually no need for a div we can just do sheet title here and inside we can just say categories and you can remove this here i think just by itself this should be enough we can also now pass the data here as in you know the categories and i think that if you click view all now it will open up right here there we go perfect so now what we have to do is we have to display all the categories but with a twist because categories can have subcategories so we have to find a nice way that when a user clicks on a category it will display all the children items if they exist Otherwise, it will just redirect. So that's what we have to do inside of here. So we're going to use that data for that. So what we can do now is we can create a few states here. Let's call this parent categories, set parent categories. use state from react and passing uh let's put null in the beginning like this and we're going to give it an option of custom category or null like this then you can copy and paste that and you can put selected category here and set selected category. And this will be an individual custom category or null. Both will be null in the beginning. So let's also call this... Okay, let's keep calling it data. I like that name actually. So let's add a comment. If we have parent categories, show those. otherwise show root categories like this so const current categories we'll check if we have parent categories if we don't it will use the data otherwise we have to fall back to an empty array like that uh okay and now what we're going to do is we're going to add outside of the sheet header let's add scroll area which we have imported and let's give it a class name of flex flex column overflow y auto height pool and padding bottom of two and inside you will check if we have parent categories if we do have parent categories we're going to add a native button element with the chevron left icon from lucid react and a class name size 4 mr of 2 and the text back so if we have entered a sub menu we can go back this way right and let's go ahead and do an unclick for now just an empty arrow function and the class name pull width text left padding 4 hover background background black, hover, text white, flex, items center, text base, and font medium. Like this. So by default, this will now not show anything. But if you change this to true, you will have a back button like this. So you can go back. Great. So we now have this. And now let's go ahead here and let's render the current categories like this, dot map, and get the individual category. So current categories are this, right? Either parent categories or the root data, which we have. And in here, you're going to render the button, again, native button. Put the key to be category dot slug, because each slug should be unique. and for the class name it's exactly the same as the one above right so you can just paste it here like this and the only thing we're going to change is we're going to add justify between after flex because it's going to have two items inside the first one will be the category name and then the second one will check if we have category subcategories and category subcategories that length is larger than zero in that case add chevron right icon from lucid react as an indicator like okay you can go further here there we go you can see that all doesn't have a chevron whereas business and money has a chevron so that's what we're going to do here now if you want you can also add a cursor pointer to these buttons depending on if you prefer that way like this so this now loads loads all categories as you can see and now we have to do it so that when you click on a category it either loads the subcategories or if if it doesn't it will just redirect to a category. So in order to do that, we have to click, we have to create a non-click here, which is going to be handle, handle, click, handle, category, click, that's a better name, and pass in the entire category, which we are currently iterating over. And then we can go ahead and develop that. Let's do it. Yeah, let's do it here. const handle category click will accept the category, which is a type of custom category. And then we're going to check if category subcategories and category subcategories length is larger than zero. In that case, set parent category. My apologies, set parent categories will now become the subcategories, right? Like this. and you can put here as custom category like that and set selected category here will be category so the reason we have to put as custom category is because it thinks that subcategories have the docs and the next page and the total docs so it's expecting this basically but we know that's not the case because of our formatted data but i'm still not inferring the types correctly here so you can just put as custom category and then an array of those there we go and now we can do else here which is basically going to do a couple of things here so we just have to prepare the router from next navigation not next router and also let's move all of this to the top like this so basically what it's going to do is it's going to check for a couple of things so first this is a leaf category no subcategories if parent categories and selected category this is a subcategory navigate to slash category slash subcategory because that's how our structure will look like so router.push selected category.slug slash category.slug like this else this is a main category navigate to slash category like that If category is all we not going to navigate literally to all We just going to navigate back to the root page Else we are going to go ahead and navigate to slash category slot. So we are making an exception for all. And after that happens, so this is inside of, yeah, in this else, basically. So make sure you do it here. we are also going to handle open change false so you don't want to do it uh we also have to develop handle open change i think yeah so you don't want to accidentally put it here you want to put it in here so only close the sidebar when we are redirecting not when we just change the category so let's develop the handle open change here the handle open change will of course accept the value if we want to open or close but it will also uh reset everything so set parent categories and then finally on open change pass in open so that's how that is going to work and you also want to use this handle open change in the actual open change here like this so now when i click here there we go now the back doesn't work yet but you can see how it opened all the subcategories right and you can see how when i close it it resets so i can now visit the photography but if i click on something like other it will redirect to slash other which at the moment doesn't work yet so now what we have to do is that when i click on a subcategory i'm expecting this color to appear right so we can do that quite easily const background color will be selected category question mark color or white and then we can just use that here and now you should have a much nicer experience there we go this looks really cool now let's fix the back button here because currently the back button is not doing anything here so i'm just going to quickly develop it const handle back click if we have parent categories set parent categories to null set selected category to null there we go that's it for the button Now we can find this and just set it here for the back button. There we go. So now you should be able to go back and you can now go to here and your URL should be slash photography slash nature. Let me show you. So it should be slash photography slash nature when you click on a subcategory, right? There we go. So I'm very, very satisfied with how this looks like at the moment. So we are still not completely finished. We are missing the individual redirects when I click on a category. And we're also missing the fact that this is not how I will be displaying categories on mobile, right? I mean, if you want to, if this is okay with you, you can do this. But I mean, this is obviously not meant for, you know, mobile here. And you can improve it a little bit. If you remember CodeRabbit from our previous pull request, we had this pull request here, search filters. It actually recommended something. Let me find it. The accessibility here. So maybe we could do something like that. yeah so besides on mouse enter we could also do like on key down or maybe on press i don't know um yeah maybe adding an on click which will toggle the drop down as well let's see if that may be an improvement i think it's definitely an improvement so my category drop down here is the focus right now you can only open the category drop down if you do on mouse enter or on mouse sleeve so how about we add on click here as well i think that it's a good idea actually so let's add toggle drop down let me just remove this pluses toggle drop down and i add it here so now when i click on it if it has the subcategories docs then it's going to toggle opening it or closing it so i think this might be a second yeah i think nothing will change for desktop mode because you are still using on mouse leave right so nothing happens but i think the mobile mode is a little bit improved actually now but let me see yeah when i click i'm expecting it to like close and it does close but it doesn't work exactly perfectly yeah if you want to you can add this i don't know you can maybe comment it out you know and just mentioned potentially improve mobile right it's a good suggestion but here's what we're going to do instead we are going to hide this entirely on mobile but just before we do that i want to add the links right so inside of category dropdown around the category.name, around this button here. Let's do a link from next link. And let's go ahead and do an href here, slash category.slug like this. But check if category.slug is all. In that case, we're just going to not add anything to the slash. Otherwise, category.slug. So that's for the root pages. So go back to desktop mode now. So if you click on education, you should get redirected to slash education now. And if you want to improve how this feels, you can also add a prefetch here, which will help us later on. But I'm not going to do that now. I would rather go throughout our code later so we can see what we should prefetch and what we shouldn't. And we also have to go inside of the subcategory menu here and do a similar thing. uh oh yes so change this so you can go inside of a subcategory menu and change this to custom category and remove the category type i think do i use the category type anywhere so we can do oh you can leave it like this actually it's okay yeah uh and this error should now go away and we are missing the proper link here so the proper href here will be slash category.slug and then slash subcategory.slug. There we go. So now if you go back here and if you go inside of music and click music theory, your URL should look like this. Music and then slash music theory. There we go. Great. So we resolved that. One thing that is left is to hide this if we are on mobile. So one way that we can do this is by going inside of our search filters. So go inside of search filters, index.dsx, and you can go ahead and wrap the categories inside of hidden LG block. like this there we go so now it's not visible on mobile at all uh oh okay so it's not visible on mobile at all but we are missing a button which i want to put next to the search input here so that's why i've mentioned you know that right now the i think that the category sidebar should fetch its own data because in order for it to populate now, what I have to do is pass the data here in the search input. So we can do that now. So pass the data inside of the search input. Data will be custom category and an array. Just make sure you added an import for the custom category. And then inside of here, you have to render the categories sidebar again. from dot slash category sidebar like this pass in the data and you also have to create uh the is sidebar open here so const is sidebar open set is sidebar open use state from react with default false now let's go ahead uh oh wait why is my okay so now i'm going to first i have to add the data here okay that was the issue and now open is sidebar open on open change set is sidebar open okay and now we have this little to do here add categories view all button so add a button from components UI button. And inside of here, you're going to give it a variant of elevated. You're going to give it a class name of size 12, shrink 0, and flex on mobile, but on large hidden, because on large we have all the categories we need. And in here just to set is sidebar open to true. And the button list filter icon from Lucid React. make sure you import list filter icon from lucid react and i think if my input size is height 12 yeah then this will match nicely and i think i need to mark um i'm going to mark the search input as use client here so i can use those and now there we go So there we go. Now as you can see I can click on the sidebar here and I can load my data right So depending later we going to add the RPC So it going to be much easier for this category sidebar to independently load its own categories Because right now, you know, we are prop drilling all the way from the layout file, right? We are prop drilling our formatted data to the search filters, to the search inputs, to the category sidebar. It would be nicer if category sidebar could just load it themselves. right okay so i think i'm gonna end the chapter here but i just want to check if that was all that i intended here we created this temporarily fixed with the custom category and we did this definitely and we even added all the links to stuff great so what we have to do now is we have to push to github and then we can review everything that we did so i'm going to shut this down and do git checkout dash b what's my chapter name 06 categories finalization like this git add git commit 06 categories finalization and then git push u origin 0 six categories finalization like this so now you should see that i am on a new branch here you can see in my graph that i have detached from master or main i'm no longer there so i have to go and create a new pull request here there we go again you can click on new pull request here and then just find this and then click create pull request. I'm going to create the pull request and then let's see what our one reviewer will tell us about our changes. You can, of course, already merge since you probably don't have that at the moment. And here we go. Summary by CodeRabbit. New features. We introduced an interactive sidebar for category selection with half. I'm not going to try navigation and a back button. Enhanced category display with a responsive layout that adapts to the number of visible categories and adds a view all button. So that is a very cool thing it noticed. We've also updated the search interface to integrate sidebar toggling for smoother use on mobile devices and we improved link navigation. That is a perfect summary of what we did. it almost matches what we've wrote here, right? And let's see what else it says here. So it gives us a walkthrough. So it noticed the database seed and the database reset file, the new types here, the custom category. And in here we have sequence diagrams for how our database fresh and database reset works, but also how our categories sidebar works as well. and it also has some nitpick comments it's nice that it hides them here because yeah they aren't exactly crucial but it's like some very small suggestions like removing this or maybe removing the question mark since we don't need it now uh it's i think it's really cool right it even like added hey maybe you should check if the category exists before you seed the category so really cool right uh great i'm super satisfied with the result so i'm going to merge my pull request and that marks the end of the branch 06 uh categories finalization you will be able to find it here uh and now as always we have to go back to our main or master branch again you can either use the dash or clear or write git checkout main or master and then git pull origin again main or master depending on what you are using and git status to confirm that everything is up to date and in here you should see that we detached and then we merged and now we are back on master branch if you want to you can run your app just to confirm everything is still working fine but i am pretty confident this is correct so we mark push to github as complete there we go this pretty much finalized the categories we might go back uh here later uh when we implement uh some proper sub routes and thing just to display like the breadcrumbs but it's going to be minimal changes and i think that this was the hardest ui wise thing that we are going to build right so ui wise front-end wise this is the hardest this tutorial gets everything from now on will be easier than this for the back end i'm not sure i can guarantee that we have a lot of things to build there but for the front end i'm pretty certain that this was the most challenging thing to build amazing amazing job and see you in the next chapter in this chapter our goal is to create trpc integration into our project we are going to do that by starting the basic trpc setup and then we're going to go ahead and make sure that we have the proper client side fetcher server side fetcher and we're also going to explore the new syntax this is something that i did not do in my original build of this application and i'm going to explain what it is in a moment. Just before we do that, as always, ensure that you are on your main master branch, ensure that you have merged all the changes from the previous chapter. And you can do that by doing git status and confirm that you get a similar message. So now what we are going to do is we're going to head to trpc.io. I would suggest that you follow the documentation in the same way I am, Simply so you know how to, simply so you learn your way around their website, right? So you can start with the quick start if you prefer it this way. This tutorial right here will teach you the most basic app router building tutorial, right? But here's what I'm going to do. So what I like to do is I like to click on using Next.js, which is here at the top. And in here, usually, if you go ahead and follow this, you're going to notice that this instruction is for pages router. So you can see that this uses source, pages, underscore app, right? This is not what we are building with, right? So if you follow this, you're not going to get the result you need. Instead, usually, what we would do is we would follow this React query integration here with the server components, right? this is what we would usually do. This is what we did in my previous courses. But if you notice, we now have another option, tan stack react query and a little star here. So what's the difference? As you can see here, compared to the classic react query integration, this client is simpler and more tan stack query native, providing factories for common tan stack react query interfaces like query keys, query options, and mutation options. So yes, trpc actually made an announcement where it introduced the new tan-stacked React query integration. Previously, you might have been used to having this thing right here built into trpc. So you would do trpc.greeting.usequery. You can see that now it has changed. You are now using useQuery natively as you would without TRPC, right? And then you plug and play TRPC into React query, or should I say tan stack React query. And while on the first site, this might seem more complicated because this is two lines instead of one, and it includes a wrapper. I actually fully agree with this decision. And I think that this is the way going forward. And they've printed out some key changes here, some reasons for the change here. So simplicity, familiarity, React, and of course, maintainability. Imagine for them what a challenge it must have been to keep their versioning of the RPC with their own 10 stack query built in. So that has to be extremely difficult for them. So I'm glad that they found a way to make this easier so we can get better updates faster. And of course, the TRPC and 10-Stack Query team, I'm sure, are in very close discussion about changes so you don't have to worry about them getting out of sync. So I am excited to try this new integration for the first time. Let's go ahead and do it. So if you have access to this, because I can't guarantee you that the documentation will change, But if you have access to this exact thing, go ahead and click on client usage, make sure you're in 10Stack React query, and click on server components. This is what you want to click on. They give you a caution here. If you want to, you can read about advanced server rendering if you want to understand the different types that exist and some foot guns to avoid. But I will try to showcase them through example later on. So first thing we need to do is add the RPC to our existing project. As you can see right here, we have to install these packages right here. So what I'm going to do is I'm going to use bun and I'm just going to copy this. But before I just run this command, I'm going to show you what are the current latest versions of all of these commands. So here are all the versions of each package. So TRPC server, client, and 10stack React query are all at 11.0.3. That is the current version that I'm using. They are all the same. And I'm pretty sure all TRPC packages have to be on the same version. So make sure you check that once you install them in the package JSON. The 10stack React query, the independent package, is currently at 572.1. Zod is at 3.24.2 and client only and server only is at these. So I'm gonna add them individually. So bun add at the rpc slash server at 11.0.3.\nThat is the first package we have to add. After this, I have to add a trpc client like this. After that, I'm going to copy this one. 11.0.3 for 10 stack react query. After that, I have independent 10 stack query at 572.1. There we go. 572.1. And then we have Zod at 3.24, 3.24.2. Client only is at 0.0.1. And server only is at the same version. There we go. So now you can see that I have a lot of changes here. So let me just pull this graph down a bit and go inside of my package JSON. And these are all of my changes. So tanstack react query at 572.1. The all TRPC packages, three of them, client, server, and tanstack react query at 11.0.3. Client only at 0.0.1. Server only at 0.0.1. and Zod, which I already had installed at 324.2. And I think that this is exactly what we had here. So all TRPC at 11.0.3, TanStack at 572.1 and Zod at 324.2. So I'm just showing you this. So because this package has changed, right? And I don't want you to have any breaking changes. I want you to be able to follow this tutorial as closely as I am. All right, so make sure you have those packages. and now we can go forward, which says create a trpc router. So in here, they give us a sample backend. If for whatever reason, you don't have this code snippet, it's a very short snippet. You can just pause the screen and copy it. We're now going to add it to trpc slash init.ts. So I'm gonna go inside of here. I'm gonna create the trpc folder and inside I'm gonna create init.ts. There we go. So we are importing init.trpc from trpc server cache from React. We have a mock context here. So this is pretending to load some user ID, right? And in here, we just do const t init.trpc.create right here. And in here, we export the create.trpc router, create color factory, and base procedure. So make sure you have this init file. so that's our sample backend now we have to create a query client factory so let's go ahead and create query client dot es so inside of the rpc folder query client dot ts and now i'm just going to copy everything here and i'm going to show you how that looks like so it looks like there is one thing that i don't have defined super json here so let's go ahead and just install that bun add super json and in my current version of super json is 2.2.2 so if you want to use the same version you can do this so make sure you have super json there we go right now i'm going to comment it out simply because i'm not using it yet i'm going to enable all of that later so if you don't have the snippet we do these two imports and we export the function make query client in here we return the query client from tan stack react query we add some stale time and we prepare some dehydration and later hydration as well that's it after we've done the query client you can also look at the default settings and what they mean here now we have to create a trpc client for client component so create a client.tsx file and this is what we are going to use to wrap our app with right so it has access to trpc so you can copy this entire thing here and paste it this one is a little bit longer and it seems like we have one issue here so let me just see if i forgot to add that yes my apologies so if you look in the sample back and we added the init file but inside of my trpc client.tsx i'm trying to look for this file and i don't have it that's because i forgot that here it is it's another very simple file so make sure you have this init file and in here just create uh actually let me see the full so it's folder routers and then inside underscore app dot ds and in here you can paste this very simple snippet so we are importing base procedure and create the rpc router from the init file and then in here you should now be able to just you know delete it and write it again or command shift and p and restart my apologies reload window and that should reset the import you should now have no errors in your client.tsx so ensure that you have routers underscore app so this is like your basic procedure here which just returns some text based on the props it's been sent let's go back inside of client.tsx. And in here, I want to do some change. You can see that we do this kind of confusing if clause. So if we have the Vercel URL, then we return it with a protocol. Otherwise, return localhost 3000. First of all, I don't know if you're going to deploy this to Vercel, right? So this is a Vercel specific environment variable. And the second thing is, I don't know if your app is running on localhost 3000. So this is what I suggest we do and make this even simpler. Go ahead inside of your .environment file right here and you can call this global and add next public app URL and do HTTP localhost 3000 or wherever your app is running on, right? So if you do bon run dev, where does your app run? You can just copy this. For example, be careful of the protocol, right? Don't accidentally put HTTPS if your app is not running on that. So now that you have next public app URL, what you can do is you can go back inside of the client, inside of the get URL function, and you can simplify this. So you can remove this completely. Just leave the if type of window is undefined. And in here, you can just return process.environment, next public app URL. Well, always double check that it's the exact same one. So you can copy it here and paste it here. So in production, when you deploy, you're going to have to remember that you have to change this environment variable to your active domain. So your domain.com with the protocol, but without the forward slash at the end. We are going to do that when we deploy. So this is basically the client.tsx, which is basically the provider for the 10 stack query and the TRPC, right? It's the client side provider. Client components need this. This is how we are going to be accessing the hooks. Without this, you would get the errors. And after you've done that, so I quickly came back to this, right? The sample back and let me close this now. We've finished this. And now we're working on the client context. And now once you've done that, you have to mount this in the root of your application. So don't forget that. Let's go ahead inside of our app folder, inside of our app route group, this root layout right here. And inside of here, you basically have to wrap your trpc provider. So I'm going to do that here, trpc provider. make sure you import the rpc provider like this uh it seems to be missing something let me just see uh trpc provider where did it import from the rpc client let me try reloading the window to confirm that this is this is an error okay i'm gonna go ahead and pause and see how to resolve it I think it's because I'm using the incorrect import here. So yeah, I should have noticed immediately. We're definitely not using this. So we should be using, what's the name of this? Let me find it here. You can't find a name. TRPC React Provider. My apologies. TRPC React Provider. That's the one we need. There we go. After you do that, no errors. My apologies for missing that. all right so make sure you have that set let's see what our next step is so this is all set and now we need to create a server fetcher right so i told you we need we have the basic router we did that we did the client side fetcher and now we need the server side fetcher so that's what we're doing now so create server.tsx inside of your trpc folder here let me just close everything else here so inside of trpc server.ts and paste everything inside and now i'm just going to go ahead and see what this is so if your router is in a separate server you pass a client all right so that's just an example i believe so i think you can remove that yeah okay i double check with my original source code and i'm pretty confident that this is all we need for the server side color so it's important that you use server only here so that even if you accidentally import this somewhere in the client uh your app will break right the build will not work and you will get a bunch of errors so you don't have to worry about any secrets leaking that's always something you have to take care of uh when you do this you know server and client side mix which can be something that happens with server components Great so I think after server that it we can now use our api there are several ways we can do this we can do it with prefetching we can do it with directly calling inside of a inside of a client component or we can also let me just scroll down even further down we can also directly just call data in a server component. So let's try just a couple of these examples. Basically, our goal is to learn how to fetch categories for the layout.tsx. So how about we learn on that example? And I think that can count as exploring the new syntax then. Okay, so we've set up the TRPC. So let's add the categories router. Let's see how we would do that, right so i'm going to go inside of source and in here i'm going to create a new folder called modules and this is where we are later going to organize all of those components which i was telling you about now in here create categories and now i'm going to create a server folder here and procedures dot ps and now inside of here you can export const categories router create trpc router from trpc init like this and let's do get many here to be base procedure from trpc init so you should have all of these things right we've added the create trpc router helper and we have the base procedure here so base procedure dot query and inside of here basically add an asynchronous method just an arrow function like this and you can do for now you can just return you know hello world for now we are we are going to connect real data in a second here now that you have this categories router we're going to go back inside of trpc routers underscore app and instead of using this hello i'm going to remove it completely and i'm just going to do categories categories router from modules categories server procedures in here we can remove the base procedure because we no longer need it i mean the import and the import as well like this so now your trpc should have access to the categories router we are going to go ahead and try that out in a second so how about we go inside of source app app home page dot dsx this is a clean clean page so we can uh try try it out here uh so we're going to try multiple methods here let's do bun run dev or wait was it running it was running my apologies so make sure you have your app running and now what i want to try first is just getting the data in a server component but i want to use uh maybe this way right so let's try this we have to import get query client let's do that const query client will be get query client from trpc server we have that we have to mark this as an asynchronous function that's important and then we're just going to do const categories and this will be await query client dot fetch query and then inside i suppose we have to pass in the trpc dot categories dot get many so trpc from the server right make sure you you didn't accidentally get another import because you can import trpc from client as well trpc and you can see how it recognizes the categories and it recognizes get many like this. And I have to run this. Or maybe, again, I'm exploring this syntax for the first time. So I'm sorry if I'm making mistakes at the moment. So let me see, maybe get many. And then query options. Okay. No errors now. I think that this might work now. So let me try JSON string if I... Maybe, I don't know. Let's see. Categories seem to have proper types for me here. And I'm just going to show you a few tips right before we try, before I go further. If you hover over categories, you should have the proper types. If for whatever reason, you don't have proper types, if you are getting errors, or if you have a type of any, there is a fix for that. And that is a common issue. So if you go inside of, let me try and find maybe extra information, frequently asked questions. It doesn't work. I'm getting any everywhere. There are a couple of things you have to do. Ensure that strict is set to true in your tsconfig.json. Let's do that together. inside of your tsconfig.json, your strict should be set to true. So that is fine. Then make sure your TRPC versions match. We know that that is okay as well. So again, all of your TRPCs should have the same version. That's another reason why it can fail. This actually happened to me the first time I developed with TRPC. And the final fix was this, adding this VS Code settings. So you can copy this and you can create .vscode slash settings.json. And it's actually recommended that you do, especially if you work in a team. So let me create a new folder, .vscode, and inside settings.json, and then just paste this inside. So this is actually recommended and you can click allow if this pops up. I would recommend having this, especially if you work in a team, so that every one of your teammates will have working TRPC. Now let's go back instead of app, app folder, home, inside of page.tsx. Try again. Oh, and now I have, there we go. So it was loading any for a second. Probably, yeah, if yours is still loading any, make sure to reload your window, right? So you need to give him some time. There we go. Now I'm 100% certain that this is working. So if I go ahead and refresh, there we go. You can see it says hello world right here. So that's how we would use this on the server. But what if this is a client component? What then? Well, first of all, we can't use async, which means we can't use await, which means we can't use the RPC server at all. So now let's go back inside of the client usage, tanstack react query, server components, scrolling all the way down to using your API. And now inside of here, we are going to skip this first example. This is the most interesting example, actually, but we're going to do that later. Now let's use this one. Hooks can only be used in client components. So we just import our good old use query from tanstack react query, and then we import use trpc from at trpc client. like this use trpc and then we define trpc here instead of the query client so you can see the equivalents right you can already see what this actually is and i think i can do this myself right so use query like this and do i need query options i need query options like this there we go and now this will i think be uh you you wouldn't actually do this you would do categories dot data right because this is loading at first this is client side we are not awaiting this for example is loading categories is loading like this so let's see and refresh here now is this working or not let me see uh 404 not oh yes i think there is one thing that i forgot to do here uh yes the reason mine is not working and yours probably isn't as well is because i think i forgot one thing here uh maybe it's here in the sample backend yes so i keep closing this but there are three things from here that i need this we need this i think after that it's going to work so we need to create an api folder trpc and then trpcroute.ts inside so let's go and do that inside of the app route group i'm going to create an api folder and then inside of here i'm going to create erpc folder and then a dynamic route erpc and then inside route.ts so basically the way client side works is by fetching your backend, your API. But if you don't give it a way to fetch, if you don't give it what to fetch, it will not be able to do that. So the reason server side worked is because server side has direct access to the database. It doesn't need to use the API, whereas client side needs the API. That's how client side components work, right? They call the server and the server writes back using an api server components don't work in that way that's why the first example worked so ensure that you have the same structure slash api slash drpc because that's what we expect here and i'm pretty confident that now this should work and it is great uh is loading is not displaying properly but the data is so we are officially managing to get the data I'm not sure why this is, oh, I think because it's a Boolean. So I have to probably maybe do it like this. There we go. So it loading false but you can see how in the beginning it true There we go So we now have examples of how to use trpc in a client component And we also did an example of how to do it in a server component And you learned a crucial difference, ironically, because we forgot to add the TRPC API, right? But you've learned the crucial difference of how these two methods work. The client side will go over the fetch API, whereas server will directly call the database. because that's what server components do. So now what I want to do, we've learned the new syntax. Now I want to prefetch the categories in the layout, and I want to client-side fetch the categories in categories sidebar.tsx. And I want to get rid of my custom TRPC types in exchange for the new inferred types from TRPC router. so what we have to do now is we have to go back to our categories router which we've separated here in the categories module so let's go inside of here and let's go inside of the layout in the app home because this is where we fetch and format the data so what do we need we need these two imports first. Don't worry if you're thinking, oh, why wouldn't you just abstract that? We will, but let me go ahead and demonstrate step by step how one would do that, right? So we get payload and we get config promise here. Let's go ahead and get that here. There we go. Payload and config promise and then let's go ahead and let's fetch our data and sort our categories by name there we go and then let's go ahead and format our data this time we don't even need the custom category because we will be able to infer this but this can be a category from at payload types like this and then in here you would just return the formatted data like this so now when you refresh this you should be seeing much more data available to you here but there are you know a couple of things that we can improve first thing that comes to mind is this it will be very annoying that we have to write this in every single procedure so what we can do is we can go inside of trpc init right here and we can extend this base procedure so let's go ahead and do that we're going to do trpc procedure dot use asynchronous the structure next because this is a middleware right so we're going to have to tell it to just continue forward after we do this and in here we are going to connect to payload because pretty much every single procedure of ours will have to talk to payload right and let's return next like this but before we do this we need the config from payload config so just add this there we go config from payload config it can be called config promise config whatever you want because it's a default export and then you have to add that to your context and the way i want to do that is by extending the context and i will just call this database if you want to you can call it payload you know whatever you prefer database payload maybe payload is more correct right and then inside of your procedures here what you can do is you can destructure the context from here and then you can just do context dot payload dot find as simple as that and you'd no longer have to import this in all of your procedures so this will now work every time you use a base procedure if you want to you can create a separate procedure called i don't know database procedure right if you are certain that you are not going to be using base procedure every time you need database and also feel free to decide maybe i want this to be database and then i'm going to have to use context.database.find i actually might even like this more i think the only reason i am kind of considering not doing it is because it has this technically you could go to database.database but this isn't really this is something else right this is the database adapter so yeah I just don't want you to be confused when you see that but yeah choose how you want to call this a payload instance I like database it's short and we can do things like this great so now what's cool is that while this also works, right? So if I go, whoops, I have no idea what this is, page DSX right here. So while this works now, you can see that categories now also have their proper types, except probably this part is probably incorrect. But you can see how this works now, right? And I am now able to infer this type further. so what i want to do next we've added the categories rather but what i wanted to next is prefetch those categories in layout.dsx so let's go ahead and do the following so prefetching will allow us to leverage the speed of server components because you saw that server components have direct access to the database but we are also going to use the client side use query simply because that's easier to work with, right? When you have used query, you can invalidate, you can refetch, you can add pagination, you can add infinite load, all of the things that are just unnecessarily difficult to do in server components. So that's why we're gonna introduce the best of server components combined with the best of client components. That's why they propose this in using your API. This is the first example they put because it is the best example to use. let's go ahead and try it out so now we're gonna go inside of app folder and we're gonna go inside of home inside of layout here and we are now going to remove this data from here we don't need it like this and instead what we're gonna do is we're going to prefetch so let's learn how to prefetch following this we need the query client from trpc server you You can also remove all three of these. So just go ahead and get this, remove custom category from here. So get query client from trpc server. That's the first thing. And then we need to void query client prefetch query and pass in this. So like this, void query client prefetch query trpc from server. Make sure you have it from server.categories. and inside of query options you would usually pass something but ours has nothing and query options don't get many right so you need to get to the actual procedure like this and now this is pre-fetched which means that the server component will pre-fetch this before it goes into the client component and what we can do now is we no longer have to pass this here we don't have to pass it like that. Instead, we're going to use the hydration boundary. So hydration boundary comes from we need to import these two, so let me just add them here at the top. There we go. So 10 stack react query, dehydrate and hydration boundary. So let's just add hydration boundary around the search filters because this is where we want to pass our data to and we have to add the state and dehydrate the query client. and here's the cool thing you might be thinking that this is trpc specific documentation this is actually react query documentation so that's why this new way of doing this is in my opinion better because you can just look at react query documentation and you will understand the trpc i think that's a great win-win situation so now what do we do with the search filters How does search filters access what we've just prefetched? Well, now we use the hook here, right? But instead of having to load it, it's going to be immediately available to us. So instead of search filters here, what we are going to do is the following. So there are two ways you can do it. You can do it how we previously showcased, how it currently is in page right here. so we can do it like this but we can also uh leverage uh okay so this is something if we want to reuse it uh blah blah blah this i think we want to leverage suspense now right uh but let me just confirm that i am doing this correctly you may prefer handling loading and error states yes i would but i just want to confirm that this is what i'm doing where does this prefetch now come from maybe this is what they've created now prefetch yeah you can see how they've kind of yeah maybe we could add those might not be a bad idea let's add this as well yeah i think this will help us actually so if you want to yeah you can go inside of source erpc server and then you could oh you have to now turn this into server.tsx so server.ts becomes server.tsx you have to import hydration boundary from 10 stack react query and dehydrate from 10 stack react query and now we have the hydrate client shorthand. So we can now just do hydrate client like this from trpc server. So you don't have to import it from there. And you can also export prefetch from here. Let me just see where does this come from. Like this. Accept this any. will it work with unknown okay I actually don like the fact that we have to use any so I would rather not do it that way I okay with explicitly typing things So I'm just going to revert this to the state it was. And here I'm just going to bring back hydration boundary like this. So hydration boundary and dehydrate from the tan stack react query. Okay, no, we're not going to complicate it. So in here we are prefetching the query, right? So this is the same thing as this. They are just using their helper. This is the same thing. And now, inside of here, you can do the following. You can wrap suspense here, like this, from React, and a fallback loading, like this. And you can go inside of search filters. And what you can do here is you can add trpc, use trpc from the client. And in here, you can get raw data. So use suspense query from 10 stack react query, trpc categories, get many. And then this data would be the same thing. But this way, you are getting it from here, right? And let me just see, did I do something incorrectly here? So use suspend query TRPC. Where am I getting TRPC from? Use TRPC. So this seems to be an error. So I'm just going to go ahead and research what it is. So I'm pretty sure this happens here because of our getMany method here. I think it's because of the way we are formatting the data here. So I'm just interested. If I return the data like this, is it still the same? It still acts the same. I just want to confirm that it's not something else here. So I'm going to go ahead and reload the window. Again, I'm exploring this syntax for the first time. So I think this is a good way to learn, actually, because you see me struggling as well. All right. So it does detect these things, but this is what it has a problem with. Must have a symbol iterator method that returns an iterator. Because if I simplify this, right, if I just did this, test hello, it still has the same issue. Hmm, interesting. How about just hello? all right so i'm gonna go ahead and explore a bit further all right i think i figured out what's wrong um so i first i thought maybe it's something with the tsconfig here because that's what uh the google says right but then uh this came to mind so bring back your formatted data here. You can import the category here. And here's how I solve, I think I solved it. Here's how. So I think they have a mistake in this part right here, because as I said, the cool thing is that use Suspense Query is from 10Stack React Query, which means that I can just look at 10Stack Query documentation. and I saw that the return is the same object as use query. So I thought, oh, okay, how about I destructure it then? And then would you look at that? It's exactly what we would expect to get. Not only that, you can see that the data still matches. I'm still not getting any errors here. But we do have to mark this as use client now. So search filters should be used client at this point. So mark that as used client and let's see what we are working with. And there we go. You can see how now you have a loading for a second and then it displays everything, right? So similarly, it works similarly as the one below, but it loads much, much faster. This isn't really a good example because this is pretty static data, right? But later you will definitely notice that having this combination, which is, let me show you, this combination of using a prefetch in a server component and then passing through hydration boundary something through here and then using use suspend query is faster. Another tip, there is a chance some of you might be getting some errors here. And I'm going to show you what I mean. there is an error reported that some might be receiving the maximum update depth exceeded. It looks like this, maximum update depth exceeded, right? I want to bring this to your attention because I did experience this. You can see that down here, I left a comment. This is how my error looked like, maximum update depth exceeded. And one solution for me was to downgrade React query to 5.66.3. So if your app is filled with those errors, right, for maximum update depth, maybe or may not happen, you can go in here and you can change the version to 5.66.3. That's where the issue does not appear anymore. but since this is a new syntax that we are trying maybe it was resolved in this new pan-stacked query integration so i'm not going to change anything from now and just to you know bring some ease of your mind um you can continue developing even if you have that maximum update depth error it's not going to break anything right but i just don't want you to be scared that you did something wrong no there's a chance that you might get the maximum update depth exceeded it uh great so now as you can see we load our our data in a new way uh and now you can decide if you are okay with uh having your data loaded this way i'm talking about uh this this part you can see the data is now just transferred here so categories has the data right uh and we just pass the data to the category sidebar. If you want to, you can make category sidebar independent of the data now, right? So if you want to, you can fetch the data. Let me just show you using your API. You can now fetch the data like this. You can get the TRPC here. And then in here, you can get the data using use query from TanStack react query make sure to import use the rpc from the rpc client the rpc categories get many query options like this and now you would have the data and you no longer have to use the one from the props uh the only you know you have to make sure that you do these changes here because data can now be optional so current categories here might be this let me just see current categories Current categories, what is it saying for data? Use query result. Current categories, custom category, oops. I think this should work just fine. Let me try it now. So now, you know, the fact that we are passing it, but okay, so it seems like it's not working. So maybe... Maybe I'm just trying my best to understand how we can do this. I think this is data. Yeah, my apologies. I have to destructure the data. Make sure you destructure the data. So what's going on now? And let's see. Okay, this, this, you can also ignore this. This is because here in the app folder we did is loading. you can remove that it's a hydration error don't worry about it the reason i say don't worry about it is because a we just did this to test out b because hydration errors uh will be solved in another way here right so don't worry uh okay so now when i click view all this actually loads independently so for example here when you click you can't really see it because it loads super fast right but if you want to yeah you can do that and then you can remove the data from here you can remove the data from here as well uh like this you basically don't need the data anymore from the category sidebar and all the places you use it you can remove the data now it's a way to reduce prop drilling right and it also in the search input you no longer need data in the search input at all i feel like this is simpler right so now your search input which you use inside of let me close everything so you can see inside of the search filters folder index you no longer have to pass the data to the search input you have to pass it to the categories because that's where we use it right so let me just separate these imports like this and now even though we don't pass the data you can see that we still load this here if you want you can go inside of categories here and you can add a special loading case if you will right or you don't have to right it's just going to be empty until something loads right i think that's okay great but one loading that i do not like is this one right it's very it hurts your eyes to look at it so what i think that we can do is just build this very fast loading state here for the search filters. So export const search filters loading. And you can return pretty much the same stuff. Make sure this is disabled like this. And make sure for this, you just give it a div with a class name height of...\n10 or whatever the height of our buttons are but i think height 10 will look just fine it doesn't have to be perfect and in here you also have to give this a style background color and you'd want to give this um let me just see so i'm not sure if you understand what what this is at the moment that's because this is from a feature we have not implemented yet i think but yeah later on when we click on drawing and painting and we redirect and this isn't a 404 this will be in the color of that category so that's why we have to specify the background color for the loading right so now that you have search filters loading if you want to you can go back inside of the layout here and you can pass that here like this search filters loading from search filters and now when you do a hard refresh you can see how the loading state looks like right there isn't too much flashing uh it still looks like it like moves a tiny bit i think that's because categories and i think the individual category drop down button uses height 11 so maybe we have to change the search filters loading to 11 there we go now there's almost no jumping at all except this part which is before we calculate which items to show and which items to hide uh great so i'm super super satisfied with this uh you can decide a convention for yourself are you going to call this loading or are you We're going to call this skeleton. If you're going to call it skeleton, make sure you change it to skeleton here as well. I think I'm going to go with skeleton, actually. I like the skeleton. And yeah, I like how this looks like. It's disabled for a second, but then it goes back. Great. So I'm super happy with this. And one thing I want to do inside of the search filters. So I also want to give this a style here. And the background color of this, right? So that's the default color that will be for this. If you're wondering why am I putting it in a style, because later it's going to be dynamic. That's the only reason. So yes, I want it to be a little bit darker than this. So now you can't even notice the loading state besides that this is disabled for a second. Great. I am super, super satisfied with how this turned out. And now one thing you can do is you can go ahead and learn how to, let me just see, I think somewhere here, they gave instructions on how to infer the output of something, right? So you can infer an output for a specific procedure here. let's try and do that oh this uses drpc like this okay uh let's let's see if we can do that or maybe maybe there's a smarter way just a second so i'm gonna go and do this i'm gonna go inside of source modules categories and i'm going to create types here i'm going to import infer router outputs from trpc server and import app router from at trpc routers underscore app so via basically this definition and we can be more specific so i just need to type and then export type categories get many output infer router outputs pass in the app router and then in here categories get many like this and then in here we can export type categories get many output single or i don't know yeah we can do single and then this would be this and then a number inside and then that would be like the single result and you can use this to replace all instances of custom category right so let's see we use custom category here and now we can use uh categories get many output here and this can be categories but we can just repeat it and i think i feel like it's easier to use a number here and you can see the types are all correct still let's see where do i use custom category i use it here so categories getManyOutput and like an item inside and this will be categories getManyOutput I think the types are fully correct, right? It's nice when things work let me just align this here let me move this here like this, so I think the types work here, let's see where else do we have it we have it inside of categories.tsx. I'll remove this here and the data here will be categories get many output from modules. And that's it. We don't have to change absolutely anything because all of our data is already compatible. And we now have category dropdown in the search filters. So I'm going to remove this. And this will be categories, get many output, and then choose one item inside so it knows it's a single item here. I'm not sure if this is the best way to do it, but it works. And again, everything works. No errors at all. And one more place, subcategory menu inside of the search filters. There we go. So this will be categories, get many output, and then a single result inside. And that's it. Fixed. And all that's left is to remove the types.ts from your app folder search filters, right? Where you had that custom type. But leave this one, of course. And then since we never used a single one, we can hide it. And there we go. Now we have all the places where you use the categories get many output and it's directly inferred from TRPC so you don't have to worry. and yeah we're gonna i'm gonna in the next chapter i'm gonna see if like this is the best way to get like the single item type maybe there's a smarter way to do it uh great i think that wraps it up for this chapter i'm super satisfied with everything we did so we prefetch the categories we client site fetch the categories in the sidebar and we inferred the trpc types we did literally all of those things so now let's push to github 070 rpc integration so what i'm doing is i'm going to shut down the app git checkout b 070 rpc integration let me just double check okay git add git commit 070 rpc integration like this git push origin 070 rpc integration there we go so as you can see now i am on a new branch here you can see the new branch here and as always you can see on the graph that i have detached now from my main master branch so now i'm going to go ahead inside of my github here i'm going to open a pull request here create a pull request and let's go ahead and see what our reviewer has to say about this And here we go. The walkthrough. This pull request introduces a new VS Code configuration for TypeScript. It also removes and updates dependency versions in the package file. Several home components have been refactored to use trpc with React query for data fetching, replacing prompt-based data with asynchronous query hooks and adding skeletal loading states. new trpc client server and api route files have been added to consolidate the trpc architecture in addition category related procedures and types have been updated to use a new structure and an obsolete type definition file has been removed exactly what we did perfect i'm super satisfied with how that turned out and in here you can also see a sequence diagram so if you are interested in how our trpc configuration actually works you can see it right here i'm actually super impressed by this great so now uh you can also see some comments left here and perhaps this one is something we should do consider enabling the super json transformer and i think we should actually do that before we merge so let's go ahead and uh do this so super json let's see all the places where we use it so the first place is inside of the rpc client we use it so we want it and we need to import super json from super json i'm just going to do it here there we go maybe I need to destructure it okay I think this is now an error because I have other places where I need to add it let's go inside of the init here I think I need to allow super json here so add import super json from super json there we go so i need it here in my init file and now this error goes away because it's properly inferred let's see inside of query.client serialize data super json.serialize and hydrate super json.deserialize and we can leave this import now there we go that's another place And let's see the server here. The server does not use SuperJSON So I think that now we are using json in all places so we modified three components now after our commit the client in the trpc which now uses the super json transformer the init file which uses the super json transformer and the query client which which serializes data and deserializes it in super json now so i suggest you do that for the reason it wrote here it is typically recommended for tRPC implementations to properly serialize dates big integers and other complex types without it you might encounter issues with these data types so let's absolutely do that uh these i'm not going to do i'm satisfied with how it is at the moment and this is just a mock context so it can stay this way so instead of uh merging uh i mean if you merged it's okay you can just push to master you know it's not a big problem so let's go ahead and do 07 erpc integration fix and then i'm going to do git push you can also push and master right if you already decided to merge it's not a problem uh but what i'm going to do is i'm just going to refresh this wait for this to receive my new changes and then i'm going to go ahead and merge and there we go so after i pushed the new fix we can see that it has some duplicate comments right here with a fallback right it's nice so the more code you give it the more comments it tells you to do and i think that now if you take a look at where it commented out you can see how it immediately marked the super json as outdated and it also marked that it has been addressed in a future commit. So that's pretty cool. Like you're talking to an actual reviewer. Great. I'm satisfied with what it currently is. So I'm going to merge my changes here. So I'm not going to delete my branch. And now let me just double check. In here, I now have 0.7 TRPC integration. Great. And as always, we now have to go back to our branch here. so that's going to be the master or the main branch so either this or main right and then let's just do git pull origin master or main and there we go that's it you can take a look at your graph and it should be something like this so i had two commits in this branch before i merged back to master branch as always please ensure that you are on the master branch and you have you are up to date you can also run your app to confirm everything is fine so you are ready for the next chapter and now we can officially mark this as well amazing amazing job in this chapter our goal is to implement authentication as you can see we are going to implement this login screen and this register screen. You can see that the register screen will have this cool little feature where it will be able to display the subdomain where the store will be available later on. But besides the UI, we're also going to have to modify the user's collection by adding the username field, and then we're also going to have to create some ALF procedures. Thankfully, we are going to be able to leverage the payload out of utils for that. After that, we're going to create this outscreens, which I have outlined above. And then we're going to combine all of those to display some authenticated states. As always, make sure that you are on your master branch using git status. Ensure that your branch is up to date. Now let's go ahead and do a quick bon run dev. to ensure that our app is in its latest state. And then what we're going to do is we're going to go ahead inside of source collections users. And inside of here, we're going to add a new field. As you can see, email is added by default because we're using the auth true option. So let's go ahead and add name, username. Required, true, unique, true. and the type of text. Like this. So the name is referring to the name of the field, of course, right? And we're adding some rules here that it has to be required and unique. And immediately after you've done that, in your payload types, you should now have access to the username inside of the user interface, right? The payload types are in the root of the source folder. If yours hasn't, a quick reminder that you need to A, have your app running so that it can generate new types. But you can also run generate types with our little script here. So once you have the username field here, what we're going to do next is create ALF procedures and use payload ALF utils. So let's go inside of the modules. And inside of here, let's create ALF. then let's create server, and then let's create procedures.ts. To help us get up to speed, we can just copy the procedures from the categories. So just paste it here in out. Let's rename this to out router. And let's go ahead and clear up the get many here. Instead, what we are going to do is we're going to create a very simple session like this, which will be a base procedure. And what we're going to do is we're going to get the headers using await get headers. And we are going to be able to import the headers. My apologies, headers from next headers, but I like to use as get headers because we have to await that, right? So I will rename this named export so that I can have this headers constant. Otherwise, it's a conflicting name. And once I have the headers, what I will be able to do is get the session using await context database.alph and pass in the headers. And then I will be able to simply return that session to the front end. So if you're wondering where does this alph come from? Well, remember, I'm not sure where do i have to go i think the init file in here we created the base procedure and we extended the database with the payload object so this is payload api that you're seeing here so this is payload dot out so you can of course go to payload docs here uh looks like the site is not loading at the moment but you can go to payload uh docs and you can read more about the out feature but basically this is how you fetch out let me try one more time there we go now it loads so i'm gonna search for out here there we go out local api so this is how you get the out by passing the headers right you can await next headers there we go from next headers basically the same thing we are doing and you can also then use login and register which is all of those things you are going to use you also have forgot password reset password a bunch of useful features here regarding out great so now that we have this what i want to do is i want to go inside of my trpc folder routers underscore app and i want to add the out to be out router from modules out server procedures and now let's go ahead inside of the app folder app route group home page and i think we can go ahead and just practice displaying the out state here right so mark this as use client here and then let's go ahead and get the rpc from use the rpc from at the rpc client and then finally in here we can get the data from use query from 10 stack react query like this and inside of here trpc out session get query options and execute that like this and then in here you will be able to json stringify data null to so now if you've done this correctly on your home page you should be able to see if you are logged in or not so let me go ahead and fine so we have permissions there's a lot of things here but basically the user is what we are interested in as you can see user now so how about we go with data question mark dot user like this so we are only focused on the user so right now it's just saying no because our data.user does not exist great so we just developed our first out procedure where we attempt to get the current session the problem is the session does not exist so now while we are here let's create the register mutation here i think i've accidentally imported this so remove that register will be a base procedure and then before we do the actual mutation we are going to have the input the input will take z as zod so let me go ahead and do import z from zod z dot object and inside of here we are going to do the following the email will be a type of z dot string dot email the password will be a type of z dot string let's give it some values minimum of one or you can you know do whatever maybe it has some passwords i don't know basically you can define the validation for the password yourself right i'm going to leave it as string for now and now we're going to set the username now the username will be interesting because it won't be just a string the username of the user will be uh what the domain will be so username dot shop dot com right this would be uh the domain because of that it is important that the domain matches the structure that we want to allow for a domain So for example we can allow the username to be two words right It has to be one word. It also can't be a dot. It can't include double. It can't end with a dash, right? Things like that so let's go ahead and first of all make it a minimum of three characters so username must be at least three characters maximum of 63 and username must be less than 63 characters and then we will do a regex right here so let's go ahead i'm just going to paste it because it doesn't make sense that you watch me type this so the regex you know what i can do here i can make it a little bit easier for you so you can either access the source code and copy it or you can just pause the screen and copy it here or you can go to the gist here you can see that in the comments i leave some pictures and some useful tips so i will add out username regex and i will just leave it here if you want it that way okay so now you have the regex for this and the error message will be username can only contain lowercase letters numbers, and hyphens. It must start and end with a letter or a number. So it cannot start and end with something else. And we are also going to add a refine here to ensure that it doesn't include double dashes because consecutive hyphens are mostly used for a preview urls so we want to protect against that like this and let's do transform value value to lowercase so we always use the lowercase version of whatever they typed there we go and now that we have this we can get ahead inside of the mutation here so let's add an asynchronous method let's destructure the input and the context here so the input will basically be the zod validation above and now here what we are going to do is we are going to do await context database create collection will be users and then data email will be input.email. Username will be input.username. Password will be input.password. And you don't have to worry about hashing. Payload handles that for us. So yes, you can safely pass the password here. This is hashed, right? This will be hashed. This won't be stored as a plain text. Don't worry. And that will be it for now. Later on, we're going to extend this. but for now this should be enough and now i want to go ahead and create the login procedure so let's make this easier by just copying the register procedure here and pasting this login login will be simpler given the fact that it doesn't have username so just email and password and here's a little tip. I would suggest that you never add validation for the, I mean, like a minimum length password for your login. For register, absolutely, right? Enforce passwords in some way. But imagine this, imagine the first day of your website, you added a minimum of three, right? And then you have a bunch of users which created their passwords. And then a month later, you decide to increase your security and you change it to six. That's perfectly fine for registration because all new users will have to oblige by those new rules. And then you think, oh, well, I should do the same thing for the login, right? Well, yes, except all of your old users now can't log in anymore. So that's an issue. So that's why I suggest that you don't do any validation regarding length or any special rules for the login when it comes to password because this is not anything that you're actually storing in your database anyway uh now mutation here let's go ahead and do the following so the first thing we're going to do is we're going to attempt uh to log in so const data will be await context database login collection users data email input dot email and password input dot password like this if there is no data dot token that means that we have to throw a new trpc error you can import trpc error from at trpc slash server in here what's cool is that you get a list of codes so you always so you are consistent with your errors and you can also add a custom message failed to log in for example great otherwise if we do have the token we're gonna have to go ahead and set the cookies so besides importing headers let's import cookies as get cookies here and then let's go back instead of the login here and what we have to do here is the const cookies await get cookies and then cookies.set name alph cookie value data token HTTP only true path this. There we go. And let's return data. Now, there are a couple of things I want to change here. I first want to go ahead inside of the auth module and create a constants.ts. So purely directly inside of the auth folder. And I want to export const auth cookie. Payload dash token. Or maybe call it funroads payload token. Something that you... I would suggest that you add like a little unique value here. just so you know it's possible that this name is used by some other websites and if you develop some other projects with payload you might have a leftover token and then you will be confused you know so i would suggest that you think of something unique something like that and then give this data token a path here is true and i would also suggest later on to do here ensure cross-domain cookie sharing so this will be important because later on we're going to have for example funroad.com this is where initial cookie will be set and then user will go to antoniofunroad.com and cookie does not exist here anymore so they will appear signed out at that point so we are going to have to ensure the same site here is set to uh none and we're also going to have to set the domain here to our actual domain but we don't have these things yet right so that's why i'm just going to leave it as a comment for now uh okay so that should set the cookie and we should never use this as a string that's why i added a cookie here so out cookie and import it from dot dot slash constants like this there we go so after you've set uh the cookie you can go ahead and return the data and now i want to copy this cookie feature and this login feature entirely and i want to go ahead and ensure that user gets logged in after they register so just paste that entire thing here and you can after you create the new user immediately attempt to log in throw an error if the login was unsuccessful and then set the cookie that's it so yeah it has to have the exact same code as this great so those are our features i think there's only one feature one procedure left which is very simple logout feature base procedure mutation asynchronous method const cookies await get cookies cookies delete out cookie that's it that's log out and now what i want to do is i want to find a way to use this methods because so far we only use the session one which displays null here so now it's time for us to go ahead and create our sign in and sign up screens so we're going to go ahead and go inside of our source app folder and instead of home we're going to create a new route group called out and inside of here we're going to create sign in and let's create a page.tsx file so this will be a page and let's return a div sign in page like this you can copy the folder and paste it and rename this to sign up and display sign up page and if you revisit your navbar inside of your app app route group home you should have the start selling going to sign up and login going to sign in so when i click this it should lead me to the sign in page if i click start selling to the sign up page excellent if you want to you can add prefetch to both of this because we will be prefetching here the user so we know whether we should allow the user to see that page in the first place let start with the sign up page as this one will be a way to create new users so what I want to do is I want to go inside of the modules inside of out, and I want to create the UI folder. And inside of here, views. So I'm going to go ahead and create this signinview.tsx. So previously, it was problematic for me to create components outside of the app folder. Because in Tailwind version 3, we had a config file, which you had to manually write in to decide which folders Tailwind would look at. So I was wondering, you know, now with new Tailwind, there's no config file. How does it know where to use Tailwind? Apparently, they invented a super smart system which scans through the entire project and detects every place where we use Tailwind. So I'm pretty confident that we should be able to just create UI files outside of the app folder here. So let's go ahead and do that. Let's go inside of the sign in view and let's start developing. So export const sign in view like this. Let's go ahead and return a div. And the div here will have a grid calls one on mobile. But the moment we hit a large breakpoint grid columns five. Now let's go ahead and create those two columns. The first column will have a background of F4, F4, F0, a height of screen, full width. On large, it's going to take colspan3, and it's going to be scrollable when needed. So this will be form column. and then here after that we're going to have another div which will be background column this one will have a class name height screen with full lg call span to hidden on mobile and visible on desktop so that's the sign in view my apologies we should have started developing the sign up view right good thing is that they are almost identical so we can copy and paste this and rename this to sign up view go inside of the sign up view and rename it to sign up view like this it's exactly the same right and now let's go inside of the app folder app al sign up page and we're just going to return the sign up view from Modulus Alf UI Views, Sign Up View. And when you click Start Selling, you should see a separation of the form column and the background column here. So now I want to populate the background column. You can use any picture. In here, I've created this picture thanks to Sora. So you can go ahead and download it if you want to. So I'm just gonna go ahead and throw it into a proper folder. it has to be inside of your public folder here. So let me just drag it here. There we go. Once you have that picture, go back instead of source, modules, auth, UI, views, signup view, and go ahead and do the following. Style. and add the following background image URL and then the name of your image in quotes. So slash out-bg.png and then you can add the background size cover and background position center and then you can turn this entire thing into a self-closing div and now when you go here it should look something like this and now we can focus on our form field so in order to do that we're going to have to import a couple of things and we can also mark this as use client because it will be a client component we are going to need form control field item label and message from components ui form you should have this installed because when we run our app we used uh chat cn i'm not sure what was the version i think it was canary yes this one and we used add all if you haven't you have to do add form and that will be true for all other components so i suggest that you you just do all to have all of them so you don't have to worry whether you have them or not so you should have all of these components and besides that we are also going to need the input and the button right here you should also have this installed this comes installed with that chat cn package right hook form resolvers you can see them right here and you should also have use form installed from react hook form so now let's go ahead and let's prepare our schema here because i think that's the first thing we should do so i'm gonna go ahead and do const form use form like this and in here i'm gonna do const form schema and for the form schema what we can do is we can go inside of our out procedures here and just copy the ones for the register like this if you want to you can also do this you can go inside of out and maybe create schema schemas.ts and export const register schema like this and just paste that entire zod thing and import z from zod and let me indent this there we go if you want to you can do it like this and now you can reuse them here so this would be register schema from dot dot slash schemas it should still work the same way there we go from dot dot slash schemas and you can go inside to the signup view and do the same thing here so you no longer have to import it uh what you can do now uh is just uh let's import so import register schema from dot dot slash schemas and then in here you can give this a type you need z from zod here as well so you can give it a type of z dot infer type of form register schema like that and then in here resolver will be zod resolver and passing the register schema and you need the default values here so email empty string password empty string and username empty string there we go so we just defined our form element now let's create the on submit method which will do uh the z infer again so values will be a type of that and just console log the values so we can see what we submit reminder to put use client at the top if you haven't and now what we can do is the following we can go ahead and add the form here with a lower with an uppercase F, meaning that it's this one from components UI form. And we're just gonna go ahead and simply spread our form hook here, right? This, we're just spreading all elements inside. Now we need to add the children inside. And for that, we use the native form element. And in here, we go ahead and give it an on submit here. Our form handle submit and pass in the on submit. this right here what this is going to do is prevent this from being submitted being called unless we pass the validation matching this schema so yeah be be careful of the password here i'm going to change this to minus three right uh okay uh i think i need yeah just save this file if you haven't so i'm using register schema here that's the only thing i changed so that i can reuse the register schema here in the form as well so it's easier and now we're going to give this form a class name flex flex column gap a padding of 4 lg padding of 16 in here let's add a div with a class name flex item center justify between margin bottom of eight here we're going to add a link to next link and an href to the root page and we're going to add a span which will say fun road here so basically this will be kind of a redirect back to the home page now i want to go ahead and visit my navbar because I want to import the poppins font to google add it here and I just want to copy this so that I have poppins ready to use and I'm going to move the link here so it's up with these imports now that I have my poppins font ready I'm going to go ahead here and give it a cn which I can import from my lib utils so let me move that to its right full place here once you have that you can go ahead and give it some default classes like a large font and then font my apologies poppins class name so poppins is the name of the constant here there we go now it looks like in our nav bar there perfect so after this what i want to do is I want to add a button here and a link with text sign in and an href to slash sign in. And we can prefetch this as well. So now you have a big sign in button. So let's go ahead and give this, first of all, an as child prop, a variant of ghost.\na size of small, a class name of text base, border none, and underline. There we go. So you should now be able to get redirected to the sign-in page as well. So now what I'm going to do next is I'm going to go outside of this div and add an H1 element with a class name text for Excel and font medium. Now inside of here, I'm going to add just some welcome text. For example, join over a thousand creators earning money on fun road. Right? Just some selling point. I don't know. Something like this. and now what we're going to do is we're going to add our first form field so below this go ahead and add a form field which is a self-closing tag give it a name of the field is going to control in this case is going to be username and then give it a render go ahead and destructure the field from here, add a form item, add a form label with a username label, give the label a class name of text base. Then let's go ahead and add a form control here, add an input, and spread the field property inside. What spreading the field property does is it makes sure that you have all the necessary values like on change on blur and all the other attributes so instead of adding them one by one we add them all at once so this is how it should look like now so what i want to do now is i want to go outside of form control and add a form description so make sure you import form description as well. I think we forgot to do that first. Now form description will have a class name here, which is going to be dynamic. So it's going to be hidden by default. And then this isn't going to make sense now, but I'm going to say true block. So by default, it will be hidden, but then later, this will be a constant. Right now it's hard coded. So we're going to fix this later. now i'm going to go ahead and do your store will be available at like this and then we're going to add strong and for now we can just put uh well we have to find a way to actively observe the username field so we can put that in a constant here const username form watch username like this and we can also watch the errors username errors form form state errors username and then const show preview can be if we have the username and if we don't have any username errors but this will not work as it should right now so replace the true with show preview and change this to render the username so if you try this now uh this won't exactly show errors as you can see this is not a valid domain errors are not showing until you actually submit the form until you press enter so we don't want that so what i'm going to do is i'm also going to add form error at the end, my apologies, form message at the end. So this should now render the errors. And if you want it so that the errors are actively shown when you type, what you have to do is you have to go inside of the form, add mode, all. And now you can see that it's going to throw an error unless your domain is looking as it should. Uh, great. So what I'm going to do here is I'm going to add a little to do here. To do, uh, use proper methods to generate preview URL, because obviously this is not a URL, but later it's going to be something username.shop.com, right? Or maybe like this. Yeah, so you can see the highlighted part. Now let's go ahead and let's copy this form field here. And let's paste it below. Let's remove the description. We are not going to need it. And change this to email. And this to email as well. So this is the field for the email. And one more time. This is for password. Password. The only thing we have to put here is give it a type of password. I would actually suggest that... Actually, I'm not sure. I think it's smarter to spread first and then override whatever was spread. I think that's the better way of doing it. So now you should have your passwords here as well. Great. So now what we have to do is we have to still inside of this form here, add a button element, which will say create account. There we go. And let's go ahead and give it a type of submit, size of large, variant of elevated, class name, background, black, text, white, hover, background, pink, 400, hover, text, primary. So now when you hover, you have this cool elevated neo-brutalism effect. Great. So now we are ready to check out how it looks like. So demo.com and demo password and click create. And you can see that these are the values we are going to submit, which is exactly what's okay with us. So let's go ahead and prepare our submit form here. So I think that we're going to need to do const trpc from use trpc from trpc client. I think that's the first thing we are going to need. And then we're going to need use mutation here from 10 stack react query. So I think that now we do const register use mutation trpc out register mutation options. i think uh i'm not used to the new uh we can call this register mutation if it's easier for you or you can use mutate here and then let's just call it register i think it's easier so register and then in the unsubmit here we're going to do register that submit actually dot mutate like this and just passing the values actually and you should have no errors here because the values should match exactly the register schema that we share between the two. And then you can disable the button here if register is pending. So if you try, you should now get errors. But if I try, for example, Antonio, antoniodemo.com, actually, I'm not sure which email I already used. So we're going to see, maybe we get back an error. we will get back an error saying you know email is taken let's try a password here demo click create account it should automatically log me in if everything was correct so if i go inside of application uh cookies i have to zoom out a bit there we go this is my fun road payload here so i think if i go back now and refresh i'm still getting null here but i'm sure if that is correct so my application here definitely shows the cookie interesting okay let's continue here i'm going to come back to that in a second i want to handle one more thing and that is an error which is supposed to happen right so test actually antonio antoniodemo.com and demo there we go so this now throws an error because these things are already taken so what we can do is we can go inside of our layout so our root layout right the one inside of the app route group and we can add a toaster from components UI Sonar and then we can go inside of the sign up view we can import toast from Sonar directly so this package you have it installed because of a chassis and UI and now there are a couple of ways you can display an error so i think you can do you can do it here on error what i like to do is do it here i think this is now the place to do it on error there we go and you can extract the error here and you can go ahead and do toast error error message like this so when you create an account let me try and refresh maybe it wasn't registered that time so antonio antoniodemo.com demo there we go the following field is invalid email so one two three here there we go and now it register now it says again that the following field is invalid but one thing that i think is doing incorrectly here is my username so my username should be unique as well so i just want to confirm instead of my collections i did set it to unique okay but i think that we have to manually do that inside of my out procedures We have to manually check that for the register here So what I could do is get const existing user like this. Let's just call this data. Existing data will be context await, context database bb my apologies find collection users limit will be just one where and then we're going to look at username equals input username like this and then we're going to get the existing user from existing data dot docs first in the array if there is an existing user throw new the rpc error code bad request message username already taken like this so i think that now we got back an error username already taken so it doesn't matter if you have a unique id unless you change your username there we go so only after you change your username are you able to do that so now we can go back inside of the sign up view here we can also add the router here do we already have the router let me see const router use router from next navigation so let me just move that here and on success here we can go ahead and router push user back to the root page right so i'm gonna do another john john demo.com demo and after success it should redirect me back to here great so now the mystery for us to solve is why does this display null so i think the best way to find this out is just to finish the login screen and then we can confirm that it's something with our script that we copied right so we can do this quite easily go inside of the modules out ui copy everything from sign up and just paste it entirely here rename this sign in view instead of register you will be using the out login mutation everything else can stay the same. You are no longer using the register schema, you will be using the login schema. So we can go inside of our out procedures here. Find login. It's a very simple schema, so we can just move it to schemas here. Export const login schema, like this. then you can go back inside of the procedures in the login base procedure add login schema and there we go now you share both the login and the register schema so now i'm going to replace register schema make sure you're doing this inside of the new sign in view right login schema and i'm just going to replace in all places and we don't need the username here login schema and this will be login.mutate and we no longer have to watch for the username errors we don't need this this will stay fun road this will say sign up and it will lead to sign up and this will say welcome back to fun road the username form field will not exist here so we can remove it the email and the password will exist and this will be login is pending and this will be login there we go so now if you go ahead and go inside of login uh we did not add this yet so let's just fix we don't need form description here we don't need zod from the out procedures because we replaced that with the reusable schemas. But what we have to do is go inside of app, app route group, out, sign in page. And we have to do sign in view right here. There we go. And now I'm going to closely observe what's going on with my network tab. So I'm going to log in into antoniodemo.com with a password of demo. It looks like I'm logged in. And inside of here, we can definitely see my token and my user here. So I'm definitely logged in, but something is going on with my session. Let's go inside of procedures, session here, and let's check what's going on here. so i'm gonna go ahead and on the log the session here and i'm just gonna refresh and see what happens in the terminal so we do have some permissions the user seems to be staying at null here now i'm also interested in the headers here i'm not sure what all is displayed in the headers but i do like to see it here and let's see a bit we have localhost here and i seem to be having this is a clerk cookie which could be from my previous developments so maybe that's why this is not working for me what if i go inside of my application here and i just clear all of my cookies and then I go into login again and demo and login. Now I have the fun road cookie. So now I'm definitely here, but it's still displaying null here. So now I want to refresh and focus on the out session here so I can see what it does. So JSON, yes, the user stays consistently null, regardless of the headers I put in. So I'm going to have to investigate why that is happening. We are definitely setting the cookie. The path is correct. HTTP only is correct. The name is outcookie. Maybe the problem is with the cookie name. Let me check local API. Log in. This is the locale, overwrite access, show hidden fields. The out. Let's try with changing the cookie. Maybe it's this, though I doubt it. I highly doubt that it's that. So I'm just going to go ahead and log in. And log in and check my application here. So now I have the cookie bowl here. So I'm sure, oh yeah, looks like it has to be the payload cookie, the payload token. Once I've added it to be the payload token, it seems to be working as it should. All right. So I'm going to stop the chapter at this part, right? So there are some things we have to do next, which is use the authenticated state. But it's fine because we can do that in the next chapter. And we can also wrap up some other things related to that area. So we modified the user's collection. We've created the out procedures. We've created the out screens. And this is the only thing that we failed to do for now. And now what's left is to push to GitHub. So let's go ahead and do that. So even if this doesn't work for you, I'm going to explore what it is. And I'll tell you in the next chapter, I'm going to try to find the documentation. But yes, using payload token seems to be working for me. As you can see now, it's working. So I am going to explore why that is and how that works exactly. So my chapter name is 08 authentication. So what I'm going to do is I'm going to go to git checkout b08 authentication like this. Or maybe what fixed it was me clearing my cookies. I don't know. Let's 08 authentication, git add, git commit, 08 authentication. Git push u origin 08 authentication. And now let's go ahead inside of my e-commerce GitHub, create a pull request. And don't merge yet. Let's see what our reviewer will say. And there we go. The walkthrough. This pull request implements a new authentication features by introducing sign-in and sign-up pages along with their corresponding view components. It also talks about how we implemented TRPC and React query dynamic session data here. And also new authentication constants, Zod validation schemas, and TRPC procedures. Great. And it also noticed that payload types have been revised. So it added that confirmation for us. As always, we can see each change that we did here. And we can also see the sequence diagrams for logging in and for registering. And you can also see how it noticed the username availability check here. It also detected a possibly related PRs, which is true, right? This is similar to the one when we created the TRPC integration. It also added some useful comments. But this is just, you know, demonstration purposes. So no point in changing it to this, even though it's completely correct in that case. In here, it noticed that we are missing the actual site URL, which is again correct. But this is just for demonstration purposes In here it suggested adding uniqueness check for the user email which is absolutely true but we know that it already being checked thanks to payload In here, it gave a tip on using secure true later when we do the cross-domain cookie, which is also a good tip. And same thing for the login. Great. So I'm satisfied. I'm going to go ahead and merge these changes for our authentication. I'm not going to delete my branch. I will just double check that I have it here. There we go. And now I'm going to go ahead, get checkout back to my main or master branch. Pull my origin here. There we go. Get status to confirm everything is okay. Make sure you're on your main or master branch and confirm that your graph properly looks like this with detached for authentication and we merged it back to master excellent so now what i'm going to do is just mark this as complete as well great great job and see you in the next chapter where i will uh try and come with a confirmation about the health cookie so we know what's going on here in this chapter we're gonna go ahead and continue where we left off in regards to authentication. But this time, we're going to focus more on the authenticated states depending on whether the user is logged in or not. But just before we do that, I want to show you some alternative ways that we can persist logged in state. Because if you remember in the last chapter, we had some unclarified thoughts about the cookie name for payload alf token. So we're going to go ahead and get on top of that. So what I want you to do is confirm that you're in your main or master branch and sure that you have merged all of your changes and you can always do git status to double check. And now what I want you to do is I want you to change the way you log in. So there are alternative ways to do that. We are currently doing it using the manual using manual next headers where we set the cookie, right? So if I go inside of my out procedures here, you can see that we set the out cookie here. So this time, we're going to do something different. We're going to use Payload's REST API. So yes, Payload actually offers us local API, which we are using right here, right? So we have this login, which we are doing, await payload login but we can also do rest api here if we do it using the rest api so let me just find there we go so our options we can just go to slash api the collection that we need login in our case that will be slash api users login so go ahead and run your app and you can quickly confirm whether that works for you or not. So go to localhost 3000. And then you're going to go to localhost 3000 API users login. Why users? Because in our collections, users, we have out set to true. So that's the collection we are using to login. So go to localhost 3000 slash API slash users login, and you should get back an error. But the error should be a very clear JSON error. You are not allowed to perform this action, right? So that's what we're expecting to see, not a 404 error. We are just trying to access this using a get method, right? Which is not something we should be doing. So what we're going to do is we're going to modify our login screen so that when we submit this data, it actually calls the REST API instead of our login method here. Just so you can see the difference. Why do I think that this is a viable solution? Well, first of all, it's a built-in REST API, right? There's nothing wrong with using it. But the second cool thing about this is that it is going to automatically set the cookie on login. So I think that's the interesting part. right it's going it's going to be easier for us so i want you to go inside of your application here go inside of your cookies so go inside of cookies and select the local host and make sure you clear all of them so make sure there are no cookies here you want to be logged out if you aren't sure you can go to slash admin here and when you are in slash admin uh it should show you the login prompt right and in case that's not the case for you so uh let me try antonio in case you are logged in the logout button is down here behind the next icon so just click the logout button. You can also clear your cookies that way. Again, then double check in your application here that you have no cookies. There we go. No cookies for me. And now we're going to go ahead and go inside of our login here. So I haven't changed anything yet. And I'm going to go inside of source, modules, alph, UI, sign in view. So where we call login. And we are now going to modify our login method. The cool thing is we are using use mutation directly from 10 stack react query, which means that we can easily get rid of trpc here and just rely on use mutation itself. So just remove that part and remove the extra bracket here. So it should just be one bracket and one kind of curly bracket to open the options. And we have on error and on success, but we are missing mutation function. That's what we are missing. So now we're going to go ahead and do the mutation function. I'm just going to go ahead and comment this out for now because we don't need it at the moment. And then what you can do here is mark the mutation function as an asynchronous method, get the values, which will be z.infer type of login schema. And then in here, simply get the response from await fetch slash api slash users slash login which we previously confirmed exists right the method is going to be post headers will be content dash type application dash my apologies slash json and then body will be stringified values if response is not okay we're going to go ahead and get the error from the response and throw back new error so that this on error captures it in here we can try to do error.message or we can default to login failed and return response json otherwise so that's simple for us to modify this login mutation. Instead of using the trpc function, we just added our own. And this is another good thing for trpc team to decide to separate those two things because you can see how easily we can just do this in case we need it. And the rest of the code stays the same. Login.mutate works just fine. And here is the interesting part at the moment. So if you've done this correctly, make sure you didn't misspell any of this. So this is why I like trpc because all of this would be type safe, right? As of this moment, it completely depends on whether I added any typos or not. So now, as you can see, you might get some cookie from next hot reload, so you can ignore that. So let's try and log in. Antonio at demo.com and demo, and let's click log in. And if you do it successfully, you can see that you automatically get your cookie assigned. so that is one potential solution you could do but still i want to you know in case you don't like this solution and you want to use your login uh right your out procedures here i do want to tell you one way you can improve it uh and i suggest that going forward you we together decide what we are going to do because it's going to be important that we handle cookies correctly because we're going to have subdomains for each store so we need to enable cross-domain cookie sharing so i would suggest that you follow the same thing i am regardless of which solution you like at the moment but uh the question was how do we find proof that this payload dash token is the cookie we need, right? We can obviously see it here. That's the name. That's the name the REST API automatically assigns. But where do we find the proof? Well, what better way than to actually search through the source code of payload CMS? That's the power of open source, right? And if you search for payload token, you can find plenty of proof that that is correct. That is the payload token they are using here. But the story doesn't end there. If you actually just search for dash token, you can find something more interesting. Payload.config.cookie prefix. So remember how I tried to use Funroad token, right? I wanted something unique. And by doing that, authentication stopped working for me. I could never find the logged in user. So here's what you can do if you want to ensure that your alf cookie is correct instead of uh doing alf cookie like this focus on one procedure now so make sure you are in the login procedure for example what you can do here is the following open backticks and then do context database which is actually payload and then in here uh let me just i think you can do dot config there we go cookie prefix and then dash token and cookie prefix by default is payload so this ends up being payload token by default but if you want to you can go inside of payload dot config dot ts right here And in the config here I think you can just pass cookie prefix fun funroad for example And then this is going to change all cookies that payload sets to a different prefix So what I think is going to happen now is that even if I use the login, which we just implemented, the REST API is going to change it to funroad token. So just to confirm, let's try it out. I'm going to delete this again. Now I'm going to go and log in and do antoniodemo.com and demo and log in. And let's see, there we go. Now, as you can see, it's called FunRoadToken. And sometimes this will stay null. That's simply because we have to invalidate the session. So just refresh. But you can see that now it works with FunRoadToken. So if you want to, that's how you can change the cookie prefix. But here's what I suggest. Let's not change the cookie prefix. Let's keep it as simple as possible. And another thing I want to do is I would rather we use our base procedures here. But I just wanted to show you that this is a completely valid way of doing it, right? This works just fine. If you like this, you can use this. But I would suggest only doing this for login, not for register. Because for register here, as you can see, we do a check for existing user by the username, which is something that their API users register is not doing. So here's what I'm going to do instead. I'm going to go ahead and I'm going to abstract this cookie creation thing. so I'm going to copy this and I'm going to go inside of my alph and I will create a new utils.ts file and I will export const asynchronous generate alph cookie which is an asynchronous method like this so get cookies like this woof my apologies this will be import cookies as get cookies. And I want to create interface props here. So let's accept the name and let's accept the... Actually, let's accept the prefix and let's accept the value. So those two things are required here. So we have prefix and we have value. And then in here, we're going to do the following. We're going to set the prefix dash token, and this will just be the value. Like this, and you can remove this for now. So generate out cookie. And now inside of procedures, let's go inside of our login first. i'm gonna go ahead and find where i use this cookie and i'm going to replace it entirely like this and i will just do await generate out cookie from dot dot slash utils like this and then in here i can pass in the prefix from context database out my apologies config cookie prefix and i can pass in the value to be data.token there we go so using this method i will ensure that my alth cookie is always properly named and always properly generated and i'm going to do the same thing here in my register method now there we go so now after i have ensured this works if you want you can also add actually i I think if you want to, you can import server only here to ensure that this only gets imported by the server modules and not by client modules. But I'm not sure if it's actually harmful if this gets imported. This is just for setting the cookies and you can access the cookies anyway. Right. So but if you want to, you can add server only, which will cause an error if you try to import this. Yeah, I would suggest you don't add this actually because it's like your global utils here. okay so we can now generate the out cookie from here uh and now what we can do is we can go back to sign in view here and we can revert it to what it was right so i'm just going to go ahead and command z this let's go ahead and command z as much as we can until it simply uses the trpc and instead of mutation function there we go it will look like this but now it should work just fine right so i'm gonna go ahead and clear my cookies here and i'm gonna go ahead and log in and now my antonio at demo.com and demo here should work there we go the payload token is set if i refresh i am logged in uh so why is it called payload token it's called payload token because we use the prefix here right context database config cookie prefix because of that and we added dash token so make sure you don't misspell that confirm that when you log in this is the token you'll get and you can now remove the out cookie constant here uh like this i'm going to remove the constants and now we have to fix our uh well actually we don't need the logout method at all because we are never going to be using it like this so you can remove the logout method so you can remove out cookie and you can remove the cookies from the import entirely so i think this simplified it a little bit so we can still use the rpc but we are using a bit of a safer way to generate the cookie using the prefix here and now another thing i want us to do is that when we log in or when we register i want to go ahead and invalidate my session call on my home page here we do the session call so it will be nice that when the user logs in this has to be refreshed because sometimes it can happen that this stays null even after we have logged in so i went and just quickly opened the trpc docs so we can learn how to invalidate so previously you would invalidate by adding trpc use utils but now as you can see you have to do it like this so we need the query client here so let's go inside of the sign in view and let's add the query client use query client from 10 stack react query make sure you have use mutation and use query client imported here and then i'm just going to separate this what we're going to do is we're going to await query client dot invalidate queries that's what we're going to do inside of the on success here so await actually we don't have to await it i think maybe we do i don't know Let's see, query client invalidate queries, and then inside, pass trpc auth session, and I have to pass in dot, let's see. Can I just do query options? Or do I need query filter? I'm not sure why they're using query filter as an example here, but we can put query filter here as well. and if we want to i think we can asynchronous this and then await this and then this will be awaited like this and then only after we invalidate we push back to the user so maybe that could be a cool way of doing it so now if you go ahead and log out by clearing your cookies it should be set to null but if you go ahead and log in it's immediately as you can see set to your new user so there's no worries whether that is working or not so now let's go and do that in the sign up view as well because when we register the user we also log in right so use query client from 10 stack react query like this and then let's go ahead and just do the same thing here there we go and mark this as an asynchronous method so now even if you clear your token again right and let's go ahead and create the i don't know mark demo.com oops so mark mark demo.com and demo for the password when we create an account there we go we are immediately logged in with that new mark account as you can see and we have the payload token right here there we go so uh now what i want to go ahead and do is um ensure that you cannot visit the login page if you are uh logged in right so if you are logged in uh you shouldn't be able to see this page right here so let's go and go directly inside of source app app route group alf route group and then let's go to sign in. Since these are server components by default, we can go ahead and leverage direct trpc access to the database, right? So go inside of server components here, or maybe we can read it from migrating. Let me just see. They have some good migrating examples here, but I think they're mostly examples for the client. Yeah, let's go inside of server components here. Getting data in a server component so this is one example that they have creating a caller maybe we can try doing that here if you need access to the data in a server component we recommend creating a server caller and using it directly so yeah it's important to understand that this method will be detached from the query client and does not store the data in the cache right so we can't just trigger this in a server component and then expect it to be available in the client, which is something that we actually expect at the moment, I think. So if I go inside of trpc server.tsx and export a color here, right? So using the app router, which we import, dot create color and pass in the create trpc context. This is with server only, so it's safe to add it here.\nSo I think that then if I go ahead and try and do const session using await, how did we call it? Color from trpc server dot out dot session. Let's see, that will be a type of out result. So I can do if there is a session dot user, in that case, we can redirect back to the root page. There we go. So this is a super quick way to handle that in server components using a caller. Previously, when we did this example, I think we used this, right? So this is where we really wanted the data both on the server and also adding it to the query client cache. so that's why previously when we did it in the home page for the server component example it was more complicated but looks like there is a much simpler way to do it using color and i think this is perfect for this example so if you are logged in now and you try to go to login you can see that you are immediately redirected back so we can do the same thing instead of a sign up page here there we go just mark this as an asynchronous component here and import the color from trpc server and import whoops redirect from next navigation there we go just make sure these are server components great so now user should not be able to visit neither the login nor the uh sign up page so that's one out state taking care of now I want this to go instead of app home and go inside of the nav bar we are going to move these components right but let's for now just wrap it up here so we have these two start selling and log in so what I'm going to do now is I'm going to add trpc use trpc from client and then I'm going to do session here and we're simply going to use query here from tan stack react query trpc.auth session query options like this and we can extract the data and call it session actually no let's do whole session like this uh so use drpc needs to be imported from drpc client and use query needs to be imported from 10 stack react query and then what we're going to do is we're going to check if we have the proper session so there we go hidden lg flex so what we can do here is wrap the entire thing in a ternary so if we have session.data.user in that case one thing will happen otherwise we're going to go ahead and show the login and the start selling button in a div right so in here add a new div there we go and this div will have the same class name and just copy one of the buttons like this actually let's see copy the start selling one actually it has the design we need and just add it here, and call this one dashboard, and redirect to slash admin, and don't prefetch this. So remove prefetch for the admin, because it's a large file. And now you should be able to go to slash admin every time you are logged in. So you can safely log out from here. Yeah, and after you log out, you will get redirected to payload login page, but we're going to see if we can modify this to redirect us back to localhost 3000. But there we go. So now you can see that we have access to these. But once you actually, you know, log in here, you have access to the dashboard. There we go, which is exactly the behavior we're going to want in the future. So I think that we outlined one more thing to do here, the dashboard button and also the library button when logged in. So let's go ahead and do the library button here as well. So we're going to have to do inside of search input here. So search input. And let's go ahead and do const drpc, use drpc from drpc client. If you want to, you can abstract. If you feel like you're repeating the code too much, you can, of course, abstract this into something like use out, use session, right? But I feel okay for now. I don't feel like this is writing too much things. But of course, you can abstract it. And then if you have a session here, there we go. To do, we added the view all button, so that's fine. And now in here to do add a library button. So if we have session data user, and I think we need to do this. In that case, go ahead and add the button right here. And the button can have a variant of elevated like this. and inside we're going to have a link with an href to slash library maybe this will change later so make sure you just import the link from next link like this and add bookmark um let's do check icon from lucid react like this and i think it can be okay for now there we go so you can see how it looks now and let's just add library text so let's give this a class name of ml mr2 and let's mark this as child so it has the flex properties there we go and maybe this is not needed actually yeah so there we go now we have the library button appearing next to the search input if the user is logged in and later we're going to use that to redirect the user to the out to the library page let's see we have some error here hydration failed because rendered html did not match the client okay so for now i'm going to leave it like this. I'm not exactly sure why this would cause a hydration error, but there are some ways we can fix it. But for now, I think it's okay like this. Great. So I think that that marks everything we need to do. So I'm going to go ahead and mark this as complete. So we learned how to use it manually using next headers. we learned about payload cookie prefix and we learned that we can also use their rest api which automatically sets the cookie on login we've set different authenticated states and now we just have to push to github so let's go ahead and push to github so i have 10 changes here it seems We removed the constants file. We added the generateAuthcookieutil. We've modified our procedures to use those utils here. And we enhanced signup and signin views with reinvalidation. We added the caller method to our server TRPC, which we then used in these two pages. We also enhanced Navbar to show a different dashboard button and a library button if logged in. Those should be all the changes. I also showed you that if you want to, you can use their REST API method for logging in. You can also use all of these methods if you prefer. For example, for our session method, you can use API slash users slash me. And I think you should be able to go to slash API user slash me, and you should get back your currently logged in user. Great. So all of that seems to be working just fine. Now let's go ahead and create a branch. So 09 out states. Get checkout B09 out states. Get add. and git push uOrigin09Auth states. Once you've pushed, you will notice that you are on a new branch here and you have detached from the master or main branch. You can now go in your GitHub repository and create a pull request. And before you merge, let's wait and see what our reviewer has to say. And there we go. Summary by CodeRabbit. So new features, sign-in and sign-up pages now automatically redirect authenticated users to the homepage. Navigation bar has been updated to display a dashboard link for users with active sessions. And a new library button has been added to the search interface, offering a quick access for logged-in users. In here, we have an in-depth walkthrough of what we just did. file by file change here and of course sequence diagrams this one is for our out pages which demonstrate what the caller out session does and how it works to redirect or to allow the user to visit or render the sign in sign up view page and in here we have how it works once we actually log including the new query invalidation here before we redirect back. It also found a related PR, which makes sense. Our previous PR was for authentication. And down here, it left a comment that we could improve this cookie here with additional attributes. And we are actually going to do this for the secure here. And we will change the same site, but it's going to be none. And the maximum age here is something that we could do. Yeah so maybe we can add these things even like immediately because yes it recommends lax because usually you should not allow cookies to go on different sites. But in our case, we're going to have subdomains, right? So we're going to have to wait to preserve that. That's why I want to do these attributes later when we actually implement those subdomains so that you can see that in action because if we just add these right now, I think we're going to forget about them and we're not going to explain what they do in a good way. So for now, I think this is just enough with HTTP only probably being the most important security feature here. Right. But we could add maximum age. I will I will explore that. Other than that, I am completely satisfied with this pull request. So, yeah, then changes here. Let's go ahead and merge this. once we've merged it i'm not going to delete the branch i will just confirm that i have it here there we go off states and now we go back to our main branch and get pool origin main or master and after that get status to confirm everything is okay and in here you should see the detachment and then the merge back to your main branch and that marks the end of this chapter as well we successfully pushed to GitHub. Amazing job and see you in the next chapter. In this chapter, we're going to go ahead and implement category pages. We are basically going to ensure that when we click on a category, we can load that page and we will also apply this specific style to our search filter component. I believe that's what it's called. We're also going to have to support the subcategory routing as well, which means we're going to have to implement this kind of breadcrumb right here. And I also want to take this time to refactor our components and put them into their respective modules. So as always, let's go ahead and ensure that we are on the master or main branch, that we have no leftover changes from our last time we were here and then we can go ahead and just do bun run dev now let's go ahead and revisit our localhost 3000 and the first thing i want to do is start refactoring this app folder app route group home so in here we have search filters footer navbar sidebar and navbar all the things that don't belong in this folder. So instead, here's what I'm going to do. I'm going to create a module called home. So by home, what I mean to say is this is only, these components only belong on this screen. You might argue that these are related to search or categories, but we're not really going to reuse them anywhere but on the homepage. So that's why I feel comfortable putting them in the homepage. So we're going to start with some simple ones like navbar so we can see if things slowly change. So go ahead and create a new module called home. And now inside of this home, let's go ahead and create a UI folder. And inside of here, components. Let's start with a simple one. So go inside of your home route group and remove the navbar from here and paste it inside of the components and you might get prompted to update imports for navbar and you can freely say yes and you're going to see how that looks like now so you can see that my layout has been modified because it has modified the navbar to be imported from the modules and i'm just going to replace this with an alias. So it's even simpler to access to. Basically, this is the refactor we're going to be doing. We're going to be adding this home related components into their respective groups. Now let's do the same thing with the footer. I'm going to cut the footer from the home route group, and I'm going to add it to home UI components, and I will update the imports. So now you can go back inside of your layout here. You can see that this was modified now as well. So I'm just going to change it to this you can also manually modify it if yours was not modified at all and let me add double quotes while i'm here now let's go ahead and do the same thing for the navbar sidebar so i'm going to cut it out from here and i'm going to add it to the components here and i will click yes for updating the imports let's see what was modified so in the layout i think nothing was modified but i think if we go inside of the home module ui and go inside of the navbar in here we might notice a navbar sidebar change so it should go to dot slash navbar sidebar because that's where it is at the moment now let's see what else we have we have the search filters which we can copy and i mean cut out entirely and add them in the components as a big folder and select yes for updating the imports let's go back to the layout to confirm what has changed because i think this is where the change happened and here it is so the search filters and the search filters skeleton should be from add modules and now all of our modules from home can be here together and just like that we have decluttered our home route group right so it only includes the pages and the layouts here great so now that we have this solved what i want to do is i want to go inside of the app folder home and i want to create the uh category architecture right so what we're going to do is we're going to create a dynamic category route so if you go ahead and create a page dot the sx here you can call this category page like this so if you click on a main page now it should redirect you to the category page so this is my local host now slash education which matches this exactly because all of these are route groups which means not a part of the url not a part of the URL, not a part of the URL either. This is the first actual segment of the URL, which means that this is localhost 3000 slash and then any value. And we are going to read this value from here. So what we can do here is the following. Interface, props, params, promise, category, string. You can mark this as an asynchronous method. And then in here, props, destructure the params, and you can do... What's the best way I can do this? Just a second. We can destructure the category from await params, like this. And then you can say category like this. now it says education, now it says business and money, now drawing, painting, fitness and health, right? So whenever you click on this, it should show you the name of the main category with no errors whatsoever. But if you click on a subcategory, it should still bring some errors. So let's go ahead and resolve that as well. I'm going to go ahead and copy this and paste it inside of itself so make sure it's inside of the category and simply change this to subcategory like this if it asks you to update imports you can say yes and then you will have this weird file in the dot next cache you don't have to worry about that just press save and make sure you close this dot next folder that folder gets updated automatically you don't have to worry about it now let's go inside of the subcategory page and besides having the category we're also going to have subcategory like this so now we can destructure both of them here so i i can now do subcategory i bet you didn't see the break tag in a long time huh there we go so now we have the category and the subcategory available right so i can go to acrylic there we go i can go to education tutoring and you can see how it works why because this is my url string in the subcategory the first part is the equal part to category and the second part is subcategory that's why we in the subcategory we can access both of them together because every subsequent page.tsx which is inside of this dynamic route, has access to its parent, right? But category, so this one, doesn't have access to the subcategory, even though this could be... No, actually, it could not be rendered standalone, no. So yeah, only subcategory can access both its parent and itself, right? So, and yeah, the params are a promise. I forgot to explain that. That's the Next.js convention. If you remove the promise from here and just try to do this where you don't await them, it will still work, I think. Let me try going. There we go. You can see it still works. But you can see that in here you get an error that category used params.category. Params should be awaited before using the properties. And you can read more about the message here. So that's why I taught you to assign it properly. It's a promise. Great. So now we resolve our errors here. And by the way, this is where I experienced the maximum update depth error the most when I switched my pages. right so in case you start experiencing those maximum update depth errors uh it could be if it really annoying you you can just downgrade uh 10 stack react query to 5 because that where i i didn see that bug happening but so far no bugs are appearing for me at the moment right So I talking about this issue right The maximum update depth exceeded because this one, right? So if you experience that, this is where I started experiencing it when I added the ability to change routes, right? When I added more routes. but now what i want to do is i want the this component search filter to actually know which route is active because sure we know it here but obviously we don't know it here because we keep selecting all as the active route and you might remember why that is if you go inside of source this time into modules go inside of home ui search filters categories in here we set the active category to be all so now it is time for us to refactor this so let's go ahead and let's introduce params from use params from next navigation so i'm going to move that here so this is the client way of accessing the params right in our category pages we did the server component way and now this is the client way because this is a client component so inside of here what we can do now is we can destructure the category param to be params.category as string or undefined it might not have the category at all and then in here we can put the category param or fall back to all right so now there we go you can see how now it works i'm able to click on music let me try it has to load the music first there we go and music stays selected same for drawing and painting pathography right and even if you go inside of a subcategory, it will still know that the parent is whatever the parent is, right? Why? Because we look at params.category. Subcategory is stored in the subcategory value. So if we were to look at subcategory, that would be a different story. But for now, this is what we need here. And also, here's another cool thing that should now work. Try clicking on self-improvement or something that's next to your view all button. And if you collapse like this, basically you can see that now view all appears as the selected one because your selected one no longer fits in here, right? So if you can see when I zoom in, since it no longer fits here, we indicate to the user that your selected category is here within all the other hidden ones right so i think that's pretty cool uh so this is responsible for that right it uh this calculates whether a selected category is hidden right this is why we spend so much time on this and i honestly think it looks amazing right it's super cool uh great but we're not done yet what i want to do now is i want to go inside of search filters index dot t s x and in here uh i want to go ahead and extract the params so i'm just going to go ahead and leave this as it is but i'm going to go ahead and do uh const params use params here from next navigation like this and in here i'm going to get the category param to be params.category as string or undefined. Const active category will be category param or all. So similar to what we did before. And now let's get the active category data. So we are going to use our data.find, get an individual category and check if that category slug matches the active category, right? Because the active category is the param. And once we are able to obtain an active category data, we're going to do the following. Const active category color will be active category data dot color. Question mark dot color. like this, or we want to fall back to the default background color. So here's what I'm going to do. Since I now have a module for home, finally, inside of that module, I'm going to create constants.ts, like this, and export const default background color to be F5, F5, F5, so I don't have to write it all the time. Now I'm going to go back inside of search filters index here, and I will fall back to default background color, which I have just imported from modules home constants. Or since we are already in the modules here, I'm pretty sure I can just like go back. Yeah, I can kind of do this, right? Since I'm already in the modules folder, I just have to exit the search filters and go all the way here. However you prefer to use it. Okay, so we now have the active category color. And now it's the const active category name. So in here, let's do active category data question mark name or null. And now we have to do a similar thing here. But for the subcategory, right? So let's do const active subcategory, params subcategory as string or undefined. And on const active subcategory name will be active category data? dot subcategories? find. for an individual subcategory we're going to do subcategory dot slug equals active subcategory and then if we are able to extract the name from that or fall back to null because it's only the name that we need in here we are calling that data we are doing the same thing right but in here we need the name so we can do it directly like this just make sure you put a question mark here and now what you can do is you can replace this fixed background color with active category color so now you can see that when you click on a specific category it changes the color to that even if you go to the subcategory so the last thing i want to do here is just add the bread crumb so we can see in here that we are in a subcategory because you can see that it's not really highlighted so user might not know where they are at the moment so we're going to create a component called breadcrumb navigation and i'm going to put that oh so this is the skeleton my apologies so just after the categories let's add bread from navigation passing the active category name active category name and let's pass in the active category to be active category and active subcategory name to be active subcategory so yeah this component won't exactly be too reusable uh but i think that's okay because we are going to be using chat cn reusable components inside right so if we want to build the breadcrumb somewhere else we have the components for that it's just that we want this specific subcategory okay let me just refresh this okay breadcrumb navigation is not defined that's completely normal inside of search filters which we have now officially moved to the home ui components module go ahead and create breadcrumbs-navigation.tsx and in here well we are going to need to import link from next link and we're going to need to import everything from the breadcrumbs from our chat cnui we can then also create our props here so active category name which is a string or null with a question mark and same thing for the active category and active category sub name so this active category is not a good prop name this is the slug basically right That's why it's a string. Let's do const breadcrumb navigation. Let's go ahead and assign these here. And now let's go ahead and just destructure all of them. There we go. And now in here, if there is no active category name, or if active category is equal to all, return null. and now let's go ahead and render the breadcrumb here. And before we go further, let's just export const here, and let's go in the index, and let's import breadcrumb navigation. Breadcrumb navigation, and I have a typo here. it should be breadcrumb navigation like this so breadcrumb navigation there we go now we have the proper import here so they are in the same folder right uh looks like i have the active subcategory oh yeah you should use the active subcategory name here in the prop my apologies so you should have no unused constants here now go back inside of the breadcrumb navigation and now you should be able to see it. For now you can only see some empty space but now we gonna to start seeing this So now add the breadcrumb list Check if we have the active subcategory name If we do have it, open a fragment here and render the breadcrumb item. Breadcrumb link. let me just properly indent these like this and passing our link here with an href of slash active category and just that and inside active category name and close the link give this as child and class name text extra large font medium underline and text primary so that's the first case uh but since this is for subcategory we first show the main category name so users can click on that and go back to the main category and now we need breadcrumb separator here and i'm just going to put a forward slash here like this and give this a class name text primary font medium and text extra large like this and then another breadcrumb item which you can copy and just move below the separator here which will be for font medium and does not need the underline nor text here and no link here actually so this is actually using the breadcrumb page and this will it doesn't need anything it can just be this like that you don't need as child either and this isn't breadcrumb yeah this is breadcrumb page and then you can just copy this last one and add the other ternary here and just render this and inside yeah yeah so here what's supposed to render here is the active subcategory name like this and in the alternative ternary it's this and there we go you can now see that you can see exactly which subcategory you are on and if you click here it will lead you back uh to the other one so i think that's exactly what we imagined here as well excellent so now uh we officially have implemented the category pages which means that we can start loading data inside of them and one thing that's bothering me is the view all button doesn't elevate so let's quickly go back inside of categories.dsx let's find the view all button is the last one here and i just think it's missing a variant of elevated here so now when i hover there we go now it hovers as well great i am super satisfied with how this turned out our breadcrumb is working nicely we have refactor all of those components into their appropriate fields and we can control pretty much all pages here yes also your all page should clear everything and just bring you to clean localhost so only when some other categories at play should it display should it show right the breadcrumbs and things so you can see that when i'm on the main category this isn't a link but when i go into a subcategory this becomes a link to go back. Excellent. That's it. So I'm going to go ahead and mark this as finished. So we show the current main category style, we show the breadcrumbs, and we refactor components to their modules. All that's left is to push these changes to GitHub. So quite a lot of files changed, mostly because of our refactoring, right? So we moved things like footer, navbar, sidebar, navbar category sidebar and all of those items in the search filters we move them to the modules but besides that we've created a couple of new things such as the pages for the subcategory and the category here and we also implemented the breadcrumb navigation here which we put inside of here. Excellent. Let's go ahead and resolve this. So git checkout dash b 10. And these, this lesson is called category pages. Let's add. And let's push. there we go now you can see that i am on a new branch you can see that i have detached from my master branch so i'm gonna go ahead in my github open a pull request create a pull request and wait for the review and here we go the walkthrough this pull request introduces asynchronous page components for category and subcategory routing by defining the new props interfaces and modifying the dynamic extraction of url parameters those are basically these two right here we also replaced our legacy search filters right which are exactly the same as our new ones we just moved them to a new place but it noticed the new breadcrumb navigation, the use of absolute paths, right? Because we changed them to modules now. So pretty cool. It also, of course, added some diagrams if you wish to better understand how everything that we've just implemented is working. And it also detected some related PRs like home layout where we added the footer and the nav bar for the first time, which we modified in this PR. the search filters because we modified the search filters this time and we introduced a whole new component inside and of course the categories because we worked with categories again and down here it left two comments but since these are just demo mock examples we are not going to apply them but still good suggestions i'm going to go ahead and merge this whole request there we go i'm not going to delete my branch i'm just going to confirm that it's here there we go and after that is done i'm going to check out back to my main or master branch and git pool origin main or master there we go after that i'm doing git status to confirm everything is up to date and of course i'm checking it visually with the graph here we detached, we merged, I am on the master branch. And that's it. That marks the end of this chapter. We pushed to GitHub. Amazing, amazing job. And see you in the next chapter. In this chapter, we're going to go ahead and build the base product collection. And as you've probably noticed, I did not attach any pictures here. That's because we're not going to be building any UI just yet. We are first just going to focus on establishing the basic products collection, and we are going to learn how to load products based on a specific category. And we are going to utilize our prefetching in React server components, along with suspense loading in the client component. We also have to refactor some components to their modules, as we did last time and then push that to GitHub. So as always, confirm that you are on the master branch, confirm that your graph looks similar to this, and feel free to run git status to confirm you are on the master branch. After that, you can go ahead and do bun run dev. So what we're going to do now is go inside of source collections and let's create products.ts. And inside of here, I'm going to go ahead and import collection config from payload and export const products collection config. Let's give this a slug of products. And then let's go ahead and create some fields here. First one being the name of the product, which is a type of text. And the required can be, should be true, of course. Then let's add the description and let's give it a type of text as well and let's go ahead and add a price with a type of number this will also be required and what i want to do now is add a relation to a category so every single product will have to have a category so it will be a type of relationship and the relation to categories you can see that if you misspell it's an error that is because it will read it will expect this slug from our categories right and put has many to be false so one product can only belong to one category besides that let's also add a name image with a type upload and relation to media if you're wondering where does media come from so media uh has already been defined here automatically when i added payload right if yours for any reason hasn't you can just add it right so create media collection as you would with any and it's just the one field here and upload set to true and so this is just if you don't have it after that you should go inside of the payload config and ensure that you have media added here that's it and then you can have images here and then let's go ahead and let's add a name called refund policy for example and let's give it a type of select So we are now going to give the user some options. You can select whatever options you want. I'm going to use this.\n30 day, 14 day, seven day, three day, one day, and no refunds. And let's put the default value to be 30 day. There we go. And I'm going to stop here. We are going to have more fields later on, but I think for now, this is enough. Now we have to go inside of payload config and import products from collections products and simply add it here like this. And now you should have access to your new products collection. So I'm gonna go ahead and refresh my localhost 3000 here and you should go to slash admin, right? Or you can just log in. We now have a simpler way, right? I can just log in now and I can go to my dashboard here, which is slash admin you might have to wait some time for this to compile and then you should have access to products there we go and when you click create new product you now have access to give it a name for example let's call this i don't know next js tutorial a description a complete guide to mastering next js and i'm going to put a price of 19 and in here we have the categories but as you can see uh it's loading ids right so how do we fix that how do we make it load the name of each category for that we have to go inside of the categories collection here add an admin field use as title and we have to select one field to use as title so let's use name here so at this point actually not name let's use uh yeah actually we can use name we can use name i think yes because it will use the id my apologies i was thinking in my head uh maybe i need to make the name unique and maybe we could do that like making the name unique uh but i think this works just fine and you can see that now you can select the categories one thing that i don't like is that we are using the all as the category one thing that i was thinking of refactoring later is just adding like we have with this button here just adding one button here hard-coded all button so that then we could exclude all from categories because we still have you know a lot of uh functions here you can see like this slash all so we could kind of remove that unnecessary checks for all if we hard-coded that button right so maybe i'm going to add a to do hard-code all button so for now i'm just going to leave it here so this is in search filters categories inside of your modules home components a ui component i just added a to do here because i don't think it makes sense that users can select all for a category right that will simply be a means for the user to filter by all categories not for the admin to put a product belonging to an all category right so let's select something else like business and money for example or if you want to make it more complicated uh what you can do because it will it will give you a better example go to localhost 3000 choose something like an inner category like real estate so i'm going to choose real estate here simply because it's an inner category so choose a sub category because that will make it more interesting to test out and i'm going to call this product product with subcategory and the price of i don't know a hundred dollars like this and you can also you know let's save this you can also go inside of products here and you can put price you can add a description i think admin description uh in u.s dollars like this and then when you refresh you can see that it will have a little uh description here in u.s dollars and maybe more descriptive price in u.s dollars there we go if you want to you can do that. Great. So now we have this product right here. We have the default refund policy, and we have a category, which is a subcategory. And now what I want to do is I want to load the product based on the category that we add here. So in order to do that, we're now going to go ahead and create the procedures for the products. So I'm going to go ahead inside of source, modules, and I think we can just copy categories and paste it and call it products. And I think, yeah, we can even leave the types because I think, basically, let's see. So let's first do server procedures instead of products here and let's rename these to products router and get many will be the only method that we need so now let's go inside of trpc routers app and let's add products products router from modules server procedures modules products server procedures here and you can go inside of your products types and you can do products get many output and in here just select products in case you're getting any type errors uh you should not actually because this is for typescript uh so now instead of your product server procedures you should go ahead and load products instead so products like this the depth one here uh will populate something else not subcategories the depth one will ensure that image is populated and the category is populated so this will populate category and it will populate image at the moment right pagination should not be false because we are going to use pagination and we don't need anything for where at the moment and uh we don't need anything for the sort yet and we don't need to format data at all we can just return data so we explore if you know things like these are even true or not so no need for this type here and now what i want to do is i want to go inside of source and i want to go inside of app folder home inside of category right here uh and now for starters i just want to try and load the products here just to confirm it's working and i think we have an easy way to do that like products await color from trpc server dot products get many i think that's it and products is the direct answer here so we can just do a break method products json stringify products so i'm doing this in the category so make sure that in here you are in like business and money something like that and there we go you can see that the category is business and money and you can see my products here and you can see the name is real estate so right now it doesn't matter which category i'm in is going to load regardless of the category but you can see some things that we uh discussed about so the category uh is loaded right the category exists but if i go ahead whoops inside of my get many and change the depth to zero and refresh i think the category now there we go the category is now an id so that's important for you to confirm if you set the depth to zero category returns an id but if you set the depth to one in that case you can see that category becomes an actual object that you can extract the name and the slug from which is going to be important for us so we can properly display this right great so that works now what i want to do is i want to create a proper component which is going to be able to prefetch this instead of call it directly and then using suspense load it in a client component so let's go inside of our modules products and let's create a ui and let's create a components folder and inside we can put a product dash list dot dsx like this let's export product list here and what we're going to use is the rpc from use the rpc from the client and then we're going to go ahead and get the data from use suspense query from tan stack react query and pass in trpc dot products dot get many query options like this so we now have a guarantee that the data will be loaded at this time so what i'm going to do for now is just return json stringify data null to so basically that's what we want with this component product list which will be marked as use client like that. So that's step one. Now what we have to do is we have to go inside of app, home category page where we did this and modify it Now I not sure if I remember how this is done so I will just revisit the RPC here Server components using your API Let's relearn how to do this. So I need the query client. Okay. I'm just going to add query client here from get query client from add the RPC server, and I no longer need the caller. and then i need trpc from the server as well so i'm going to grab it here and then in here we simply do void query client dot prefetch query and i pass in trpc products get many and i think i need to pass the query options like this there we go and i think that's it the next step is to add a hydration boundary here so instead of this i'm going to add hydration boundary right here and i'm going to pass in the state dehydrate from 10 stack react query here and i have to pass the query client so let me just pass it there we go and let's add product list here and i don't need to pass any props whatsoever so i'm just going to do a cleanup here so for now yeah we don't need any params i think i can do this right so i don't have to remove this yet and let me just align these properly so now we are leveraging this server component which is going to prefetch my products get many and product list here is going to already have them loaded thanks to use suspense query but we're also going to add export const product list skeleton here which can be a very simple div loading for now so now we can import that as well product list skeleton and we can just pass it here suspense from react fallback product list skeleton like this let me just move that here we can collapse these two and let's try it out so this should be working on the category here so if i go here in localhost in a category nothing should be different right now right it just works very fast and you should see a small loading for a second it's very hard to see but we can add an artificial loading here so for example here's five seconds of artificial development time so let's refresh and there we go you can see how it's loading one two three four five and then it loads great so our prefetching seems to be working here no problem so what i want to do now is I want to load the correct product based on the category, right? So we can then throw the same component in here in subcategory, and it should all be working fine. And it's going to be particularly interesting because technically, if you set a category to mental health, you should load that if you click mental health, but also if you click on fitness and health, because in the parent category you should also load everything that is set for the parent category but also every single subcategory right so that's what we're going to do now so let's see where we are we have added the products collection and we have started this chapter right here so let's go ahead and do it so i'm going to have one product which is real estate and then let's see can i duplicate i can i'm going to duplicate this and this will be product with category uh and the category here will be business and money and i'm going to click save so now i should have two products one uh let's see can i decide which columns to see you can click on category and now you should see category so one is the main category and one is a subcategory so now i have enough to test both this click and this click all right so we first have to go back instead of get many here and what we have to do is we have to add the category here so i'm going to add an input here z.object and start building my input import z from zod let's organize this just a little bit nicer like this i like to move this like that and let's invent this let's invent this and i think the whole thing actually needs to be invented there we go so i'm going to add the category the category needs to be a string nullable and optional and now in here we can extract the input so now what i want to do is i want to check if we have the category or not so before i do anything i'm going to do if input dot category like this and if we have the category what we should do is we should get the category slug and we should add it to the because the why category slug right perhaps we should be like descriptive maybe category slug is a better decision here but i'm going to leave it as category for now so this is not category id why are we not sending the id because we don't have it when we click here the only thing we have here is education right uh and i seem to be having some errors here that's fine okay uh i mean if it's causing you some lag you can close localhost if it's causing you some repeated errors here so if we have input that category what we have to do is we have to fetch this category so let's go ahead and do const uh data categories data will be await context database find collection categories limit one pagination set to false where slug equals input category and now we should get the category so let's do category categories data docs first in the array so that should be the only one here and now what we should do is we should prepare the where object right so in here we will pass where so the way i want to do that is by preparing a const where to be a type of where from payload so i'm going to invert it and i can just do import type where and by default it's going to be an empty object and now i'm going to do if i have the category here i'm gonna do where category dot slug equals category dot slug like this so at this point you actually might be wondering why didn't i just put input dot category in here right input dot category well i think that we are going to need to fetch the category either way because we are going to need to load all sap categories but i also think it's like a good uh way to check if what you're trying to load actually exists right so yeah technically we could have done just this so i can like console this out and just use where category slug and i think that at this point this could work uh and i just think that i need to yeah so we are definitely always going to have options inside so let's first go inside of the page where we prefetch category page here and in here we have the params and in here i I can do const category await params like this. And then in here, I can pass the category like that. There we go. And then in my product list, I should also be able to access the category. One way we can do that is simply by introducing an interface props category. and let's make it an optional string this props category and pass in category and there we go now it matches right what we prefetch now matches what we put inside of the use suspense query so right now i think that we should no longer be getting any errors here you can see that all is still loading. Let's see the issue here. So this is inside of my navbar, I think. This is what's, yeah, the link to the library. I'm going to explore this at one point, but right now I feel okay like this. So try something other than all. So if I click on design, it looks like I can still get everything. So let's see why that is happening. uh i think it's happening because i'm not passing the category here so let me try refreshing now okay so it's still loading everything so i think i did something incorrectly here let's see what happens in the procedure uh well what happens is that i never assign the where in my procedure there we go so you can see that now design all of this have zero items But since in my products here I have one with business and money i so a main category if i click here there we go you can see how it loads let see how many it loads uh how many items total docs just one item which is what we expect right so now we can do the same thing for subcategory so let's see the title of this project of this product the title is product with category so it's the one we expect this one how do we load this one product with subcategory that's the complex one right so the first way to load it is very easy we can literally copy the entire page here go inside of subcategory and just paste the entire page here but instead of having a category here we're going to have subcategory but we're not going to change this we can just do subcategory here like this so the prop name will stay the same it's just that we know that we are working with a param called subcategory and we can pass that here as well so i think that this should still be okay so now when i click here nothing but i mean uh it doesn't show that product with subcategory but if i click here and go to real estate there we go product with subcategory so it works and i just need to check on this to make sure that i think this just happens in the middle of hot reload let me just see going back going forward yeah i think that happens in the middle of hot reload but you can now see that we get different results in the main category and in the subcategory so my goal to wrap up this chapter is to be able to load in the main category both the subcategory and the main category so in order to do that we have to go back to this we first have to actually load the category's data here. And then what we have to do is we have to check if we have the category. And then if we have the category, what we would do right now, right, is use that category. Actually, I'm going to call this main category or parent category. If we have parent category, we would do parent category.slug. right so now we are getting uh into loading this category and now what we have to do is we have to check if this what we assume is a parent category has any subcategories right let's go ahead and do that now so what i want to do here in the procedures is quickly visit my categories procedures. Why? Because in here we have this, the formatted data here, because remember, right now the depth does not change the types here. So what I'm going to do is I'm going to copy my formatted data here because I like it. And I'm going to go ahead and try that right here. So after I get the categories data, I'm going to go ahead and get the formatted data based on the categories data here like this and i'm going to import docs category from add payload types like this so now i have formatted data and let's also add depth one here so we know that doc will be a type of category right we can copy the same thing here that one means this so i can add that here just so you are aware of it right there we go so now we have the formatted data here and now what i'm going to do is i'm going to prepare const subcategories to be an empty array like this and instead of doing this immediately i'm going to get it out of this if clause and i'm going to do if we have the parent category let's do subcategories.push and simply spread main category, my apologies, parent category, subcategories.map, get the individual subcategory and extract subcategory slug from it like this. And I think I'm having some types, script errors here, let me just see. I think this is an extra if clause. Yeah. And there is no semicolon here. So the error is only in this part. So it says that this subcategory is... Just a second. Yes, my apologies. The parent category should come from the formatted data and the first in the array. So after we formatted and defined that subcategory is a type of category, then you can see we can freely access this right here so we just have to ensure that we add depth one otherwise it's going to break our code completely there we go so now what we're going to do here is where category slug instead of equals is going to be in and then what we're going to do here is an array where we're going to put the input category or what we can do is just parent category.slug and then simply spread the subcategories which are filled with slugs right so we can do subcategories slugs maybe i think that's more precise whoops so just subcategory slugs subcategories slugs there we go and i think that now our business money our parent category should load two items. And I think I can already see it does. Let me see total documents two. So the first one it loads is product with category. And the second one it loads, let me just find it, is product with subcategory. Great, so it loaded it because it detected that business and money has a subcategory of real estate. But if I click on real estate exclusively, and then it detects that there are no subcategories here. So it only loads the subcategory values. Perfect, exactly what we wanted. Amazing, amazing job. So that's where I'm gonna step here. I think that we do have some more things on the itinerary, which is to refactor some more things. So we're gonna check that in a second, but I just wanna show you the depth is very important, right? So try using depth zero. And this should break it, right? So right now you can see business and money only loads one page, right? So it's a good thing that it even works. But if you do depth one, it will populate the subcategories, which means that you will have access to each subcategory slug. That's why it's important to do depth one. If you are unsure, you can always do a console log of your category's data. like this. So refresh and then go to your terminal. And there we go. You can see that in here, you, yeah, maybe use JSON stringify instead. No, dude. Use something like this. And then in here, you're going to see a nicer way here. Let me try. There we go. So you can see this is how it looks right now. I think that's because, huh, that's interesting. Parent. Oh, I think that the reason this, what we're seeing here is not something we use, right? I think this is simply because for each subcategory, it also loads the parent that it has, and then it limits the depth to here. So I think that this is okay. Let me just try and scroll down here. and i am interesting about this a query was dehydrated as pending and project products get many input category huh this is interesting i will explore what's going on here but for now i think that we did a good job right so what i wanted to do in regards of refactoring uh was instead of source app folder app home inside of page here uh well there isn't much we can do here but let's try layout um yeah we can do layout actually so i'm thinking whether we should or maybe this is okay the what i was thinking with refactoring was creating inside of modules instead of home creating layout folder but i'm not so sure that that's the best decision really because this way it allows me to go to the suspense like this so i think i'm going to keep it for components and i will allow layouts and pages maybe we'll we will see but for now i think I'm actually satisfied with this so I am going to end here and I will of course explore these errors that are happening here it looks like somehow our favicon.ico is being passed as the category name and I think the reason that's happening is because of our structure here slash category slash subcategory. I think that by default, the way favicon is loaded is by going to localhost 3000 slash favicon.ico and then that confuses it and turns it into this infinite load type of thing. So I will explore why that is happening. But for now, I think we have proven that our code here works. so let's go ahead and do the last thing we need here so we load products based on category we prefetch in react server components and we load with suspense in client components now uh we are going to skip this part at the moment i still have to decide whether i want to do that or not and what we have to do is push to github so this chapter is called 11 products so what i'm going to is I going to close this and get checkout branch 11 products That right Then I going to do git add and a dot git commit 11 products and then i'm going to get push u origin 11 products and now you will notice that we are on a new branch here 11 products so what we have to do is go inside of our github here and open a new branch. I mean, open a new pull request. And now let's wait for our reviewer to see the changes. And there we go. So the walkthrough. This pull request introduces several updates across the code base. Two-page components have been modified to adjust parameter handling, switching from a category to subcategory in one case, and integrate asynchronous data fetching using a query client, React query suspense, and hydration boundary. As always, we can see each file change by change. And here we have a sequence diagram, which helps with explaining what is going on. So user navigates to the product page. We execute prefect query, either with category or the subcategory here. and you can follow the entire order of events happening with a diagram. I think this is super useful whenever we implement something new like this, right? It's absolutely amazing. We can provide prefetch data to the page component here. We can see the getMany called with provided parameter once it actually loads. It's absolutely amazing. So all the way to the database to where it returns the product data. it would be nice if it also understood like the query client cache maybe so that would like further establish what's actually going on here it also noticed the related pr which is true and it added some comments it's mostly comments ensuring that we have the parent category and ensuring that we throw a state that we handle a case where the category is not found right we have to decide what to do with that but at the moment i am satisfied with this let's go ahead and merge this pull request i'm not going to delete this branch i will simply confirm that i have it here there we go 11 products and now i'm simply going to go back to the branch i was previously on whoops so main or master and git full origin main or master should bring you up to date and you can always do git status to confirm that everything is fine and i always like to check the branching to confirm i branched out with products here and then we merge those changes back there we go that's the chapter finished we pushed to github amazing job and see you in the next chapter in this chapter we're going to go ahead and build some basic product filtering here so this is a screenshot of the component we are going to build and i will primarily focus on the price filter because tags require a whole another entity, right? So this is what I will focus on. But before we do that, in the previous chapter, we had an unresolved error with our prefetching. And that gave me an idea to improve TypeScript strictness inside of our app and resolve some TypeScript errors. So as always, ensure that you are on your main or master branch so that you have all the latest changes, run git status to confirm, and then do bon run dev. So this is what was happening. Instead of source app folder, inside of the app route group, you can see that we have a favicon.ico. And inside of home, you can see that we immediately have a dynamic category here. What does that mean? So that means that localhost3000 slash favicon.ico and localhost3000 slash business and money are treated as the same routes. And that is problematic for us because when we go inside of the procedures for the products, we don't check if the parent category exists before we give it a slug here. So that's what you're going to see happening now. So let me just try and go into a localhost 3000 here so I can demonstrate that issue happening in real time. So click on business and money, perhaps, maybe refresh, and you will see that we have that error because it's going to be easier to look if you remove the console log here. So I will just remove the console log. There we go. You can see that we have a get slash favicon.ico, right? You can see that they are on the same level. So our code gets confused and it thinks that this is a name of a category. And that's why we run into this error. So it treats favicon as a category and then it's attempting to read the slug from that category. There is an easy way to fix this, which would simply be adding a new route in front of category. So for example, you could create a folder called C and then just drag the category inside. right and then all of your routes would have to be changed from localhost 3000 slash business and money to slash c business and money which would resolve this issue right because fatican would never get treated as a category that way so if you want to you can do that but then just make sure that inside of your modules home ui search filters you can find in folder and search for link and then you would have to modify all of these to have that prefix as well so that's one way of fixing it but here's a better way we can do if we really want to keep this structure. So there is a bug here that we were not alerted of. As you can see, the parent category here is automatically assumed to always exist. Even if I did formatted data 10, you can see that it never doubts that parent category.slug exists. But we know that we don't even have 10 products in our database. So how can TypeScript convince us that this exists? Here's a rule I want you to turn on immediately. I want you to go inside of your tsconfig and after strict, go ahead and add no unchecked index access set to true. And once you save this right here, you will see that we now have an error because this is potentially undefined. An easy fix is to just move it inside of this if clause where we check if we have the parent category in the first place. So that's an easy fix for this. And I'm now going to remove this, what I've wrote here, and I'm going to do some refreshes now. And you will see that from now on, this will be the last time you will see this error right here so let's go ahead and try again let's refresh let's click here let's click here let's click in these ones these ones right just go ahead and click on as many of these as you want and you will see that our favicon gets hit but we don't match it as any kind of category right now the problem is that it's still technically attempting to hit your category so if you want to you could do a check you know if input dot category and then just skip that but you can leave it like this for now i'm gonna decide later if maybe we will refactor this to the example that I showed you with a prefix slash C, right? Now, there's another thing I want you to do. I want you to go inside of modules. My apologies. Inside of, yes, inside of modules, home, UI, components, search filters. And I think categories sidebar has a type error here. Categories get many output. it. It has an error here because of our, let me just find it, categories server procedures here because of this line right here. You can go ahead and remove this line. I just wanted to ensure that our subcategories don't have subcategories, but looks like that actually messes up the types. So you can just remove that line from your categories router, get many. And now if you go back inside of the categories sidebar, you will see that we no longer have any type errors here. There we go. And keep in mind, after you've changed your tsconfig.json to this, some more type errors might pop up now because you can see that it's more stricter this way. All right, let's go ahead and let's develop our filters now. So what I want to do is I want to go inside of modules, inside of products here, inside of server, and then inside of procedures. And besides the category, I am now going to add a minimum price, which will be a type of string, nullable, and optional. And then I'm going to duplicate this and add max price here. So we're going to have both minimum price and maximum price. And then in here, I'm going to If we have input minimum price, where.price will have a greater than equal input minimum price. And then we can duplicate this as well and check for maximum price. And in this case, it will be less than or equal maximum price. There we go. That's the backend part of the app.\nof our price filters. So what I want to do now is I want to go ahead and install a package which will help us with the front end side of things. And just in case you're getting an error like this, so I just went ahead and checked my error here, client must be connected before running operations. So I immediately googled this and it looks like it's Mongo client getting disconnected, right? So it's probably that what happened. There's nothing we can do on our site, I believe. I can try refreshing and you can see that now it works. So it looks like it was just some access to MongoDB, which got broken. Now let's go ahead and let's install Nooks into our project, which is a type safe search params state manager for React. And it's going to make your life much, much easier when it comes to params. so I'm gonna do bun add nooks and I would suggest that you wait until you see what version I have so I'm gonna do bun add nooks and you can see it's 2.4.1 so what you should do if you want to use the exact same version is install this great after we have added nooks let's go ahead and click on adapters and select next js app router so we have to import this inside of our app layout let's go ahead and do that so i'm going to go inside of source app folder app root layout right here i'm going to go ahead and import this from nux adapter and i'm simply going to go ahead and i will wrap my entire app including the trpc provider inside just like that now what i'm going to do is i'm going to create a use product filters hook so i'm going to go inside of source and I'm going to go inside of products, modules here, products, and I will create a hooks folder and inside use product filters.ts. And then I'm going to import use query states from Nux. And I'm going to go ahead and do the following. I will do export const use product filters. And I will immediately return use query states here. And inside of here, I'm going to define the minimum price and the maximum price filter. So minimum price will be parse as string. And I think you can import this from Nux like this. and give it some options clear on default to be true. So this is just defining the behavior we want at the moment. And you can duplicate this and rename this one to maximum price. So for now, these are all the filters that I want. And now let's create a component which is actually going to use this. So what I'm going to do is I will just focus on my category here. You can remove this C if you didn't use it. It was just for demonstration. I'm going to focus on the category page. So for now, I will not do anything in here. So category page. And what I'm going to do is besides having a product list, I'm also going to have a product filters. so i'm just gonna go ahead and create a little uh scenery here right i basically just want to improve some things there we go so now just by refreshing uh yeah make sure you are in the main category right you can see that subcategory has no padding but main category has padding that's because that's where we are working now so make sure you are there and now what i'm going to do further is i'm going to create a div a grid so div class name grid grid columns one on large grid columns six on extra large grid columns eight gap y 6 gap x 12 that's going to be the grid for the two elements which we're going to have here one being a new product filters let's give it a class name border and padding of two simply so you can see there we go product filters so what i'm going to do now is i'm going to move product filters to be uh before the docs there we go so you should now see the product filters before you can see the docs. And what we're going to do now is we're going to give this product filters another wrapper here, class name LG, and it's going to be call span two, XL call span two. Let's do that. and inside of here around the around this suspense let's do a class name of lg column span 4 and excel column span 6 like this so yeah you should now have space for the product filters and you should also have a nice space for the actual product list here and now what i want to do is i want to enhance um the products here just a uh just a little bit so you can at least see them as some cards so i'm going to go inside of the product list here and in here i'm going to give them a class name of grid grid columns one sm grid columns two and the grid columns two lg large grid columns two excel grid columns three to excel grid columns four and gap four and let me just double check that all of these actually exist that i didn't misspell any this is a nice way to check that there we go and then what i'm going to do here is just product dot my apologies data question mark dot docs dot map and then get the individual product here and in here we're going to give this a key of product dot id like this and you know i just want to give it a little class name border rounded md background color of white nothing too drastic for now and let's just give it an h2 element product.name right so just so we can start seeing some things appear here product.name and i think for now this actually might be enough and maybe let's add product.price here under paragraph here and let's give this a class name of text excel font medium so i think that now we should at least see uh these like that and let's give it some padding just so it looks like something great and now it will be easier for us to see the actual filtering going on here so let's go back where we started building our product filters and let's actually focus on product filters so i'm gonna go ahead inside of my modules and i'm going to go inside of products inside of ui components and i'm going to build product filters dot ts let's export const product filters like this and let's just build a nice little div here and make sure to name this tsx let's give this a class name of border rounded md and background color of white let's go ahead and create another div here and let's give it a class name adding for border bottom flex item center and justify between and now in here we're going to add a paragraph filters and a class name font medium now let's go back to the page and let's replace this div with product filters just like that there we go so now you should see the text filters right here excellent so what i want to do now is beside it add a button clear with a class name of underline so this is a native html button element and the type of button like this and we have to mark this as a client component so we don't get any errors here there we go perfect so now what i want to do is i want to build a reusable component called product filter so first i'm going to create the interface product filter props and then const product filter is going to use these props here whoops like this and we can just extract all of them and in here we're going to set is open and set is open so basically we are building like a little accordion here make sure to import use state from react then if is open we're going to use chevron down icon from lucid react otherwise chevron right icon and i'm using the constant with a capital i so it can be used as a component so let's go go ahead and return a div here, like this. And let give it a class name of cn which we can import from lib utils and pass in some defaults like this So padding for border bottom flex flex column and gap two and then pass in class name if needed, which we have as a prop here, as an optional prop. Inside of here, another div with an on click, which will simply toggle set is open based on the current value and reverse the current value, and it will have a class name flex items center justify between and cursor pointer like this and inside let's add a paragraph with the text title now let's have a class name font medium here like this and then let's render the icon the icon is this constant right here and let's give the icon a class name of size five. Outside of this div, if is open, we render the children conditionally. So now we have an easy way to render or hide some things, right? So what I'm gonna do is I'm gonna go back inside of my product filters and outside of this div here, I'm going to add my product filter and give it a title of price. and inside I will just add a paragraph price filter and now you can see that we have a price filter here which we can click and it opens just like that and yes we have a double border here but don't worry about that that's because this will not be the last filter that we are going to have but if you want to you can give it a class name border b zero there we go and you can see now it looks better great so we can now open and close this filter and now let's go ahead and let's actually uh implement the price filter component so back in the modules for our products here products ui components let's go ahead and create a price filter dot t s x let's give it an interface max price minimum price on minimum price change and on maximum price change and let's prepare a util format as currency basically we want whatever the user types inside to immediately be uh to immediately be formatted as a proper currency value so const numeric value is the first thing we're going to transform this on so value replace and inside of here you need this regex right here there we go so we are basically targeting the numbers something that's not a number and we replace it with an empty string like that now let's go ahead and ensure some decimal points so numeric value split by dot like this formatted value will be parts first part of the array plus parts dot length is larger than one it means we have to add a dot otherwise let's attach to an existing one question mark dot slice on the second position or simply return an empty string so this is a bit confusing um basically it's just ensuring that the decimal points look correct with this formatting method right now let's go ahead and do this if there is no formatted value it means that this isn't a valid number so let's just return an empty string now let's do number value here and do parse float formatted value because we can properly parse it now because we checked with this and let's do one more check if is not a number number value in that case we can return an empty string again because something's not correct and finally we can return new intl which is built in uh api and us style currency currency usd minimum fraction digits is zero maximum fraction digits is two dot format number value which we confirmed is always going to be a number so now what we need is a few things. Let's mark this as use client and let's import change event from react and input and label from our components. And now we're going to be able to finally use this price filter with minimum price, max price, on the minimum price change and on maximum price change with the props here and what i want to do is i want to return a div with a class name here flex flex column and gap of two and before we go any further i want to go back inside of my product filters and i want to add my price filter here you're going to have some errors even after you import it because it's expecting some types some props which we don't yet have but at least you will be able to see what it looks like so keep it open so you can see what you're developing now let's add another div inside with a class name of flex flex column and gap of two let's add a label here minimum price and let's give this label a class name of font medium and text base like this and below the label let's add the input component the input component should have a type of text and it should have a placeholder of zero dollars and then value will be minimum price if we have it format as currency minimum price or just fall back this and on change here for now an empty arrow function let's go ahead and let's copy this entire div and let's change this to maximum price and the limit for maximum price will be i will just use the infinity sign you can find it on google just google infinity sign and you can copy it and paste it here and instead of min price we use max price there we go so now you should see those two right here but you are not able to change them at the moment so now we're going to fix that so let's go ahead and implement the handle minimum price change so handle minimum price change takes the event which is a change event and takes in html input element and inside of here we get the raw input value and we extract only numeric values so event dot target dot value and then we go ahead and we replace all non-numeric values with nothing so the same thing that we do right here right but we just do it before it even hits the on minimum price change because on the minimum price change will be what's assigned to the filter so that's the handle minimum price change and now we do the exact same thing but for the handle maximum price change right event target value that replace you can see this line is exactly identical but we just call the on maximum price change right here great so now let's go ahead and assign the values here the minimum price will be handle minimum price change and this one will be handle maximum price change like this um i'm not sure why i'm getting this here but i think if i remove use client then it fixes so i just have to be careful i have to use price filter inside of another parent component which has used client which in this case is not a problem uh so now i'm still getting some errors because it cannot call the on minimum price change so this is what we finally do now if you remember we added use product filters here so we can go ahead and finally use them now so i'm gonna go ahead and import them here inside of the product filters um my apologies not product filter product filters here const filters and set filters from use product filters so make sure you import them like this hooks use product filters and then what i'm going to do is i'm going to do const on change here key type of my apologies key of type of filters and the value will be unknown whoops like this and set filters so we are basically creating a reusable way to change any filter inside whoops my apologies there we go and now what we can do is the following we can assign these two props to the price filter the minimum price and the maximum price coming from our filters minimum price and maximum price and we can call our reusable on change for both of them quite easily on the minimum price change calls the value on change minimum price and on maximum price change calls the value on change maximum price there we go so now what should happen is that in your url as you can see i currently have nothing in the url but if i type in 500 here you can see that my url changes to minimum price 500 but if i go ahead and type in a thousand in the maximum price you can see that now it has both of those values and if i go ahead and remove one you can see that now they are empty for example right so this is what knucks those for us this is what we are going to play around with uh so what i'm going to do is i'm going to end the chapter here and then we're going to continue with the filters in the next chapter so uh this is what we aspire to do in this chapter we also had to resolve some typescript errors and we added some basic product filtering but only as an introduction right so uh yeah not exactly right we didn't add any actual filtering but we did prepare the ui component at least now let's push to github so i satisfied with what we done so far it only ui but it fine We also fixed an important bug Let do git checkout b12 filtering products right Let me just see. Filtering products, correct. Git add, git commit. 12 filtering products. There we go. Git push u origin 12 filtering products. And now let's go ahead inside of our GitHub and let's open a pull request here. Whoops. And let's just create a pull request and let's see what our reviewer has to say. So let's see what we've done in this chapter. We introduced an enhanced product filtering interface with interactive price filters for setting minimum and maximum values. values we updated the category page layout to display filters alongside a responsive grid view of products that is exactly it we can also see the step-by-step walkthrough of every single thing that we did here we can see of course the sequence diagrams explaining how entering the minimum and maximum price notifies the price change updates the query states request products with minimum and maximum price state which is something that's not happening yet but it is the next step you can see how it already anticipates that we're going to do that which is exactly what we're going to be doing but we don't yet do this part but still very very impressive for a zero configuration tool right here and it actually noticed a critical bug so yes these two actually override one another Because if you have this, you would set it to this price, and then you would just override the where.price, right? So it offers a potential solution here. If it has where.price, then where.price less than equal is maximum price, but also keep it this. But I think that there is an easier way to do this. So I'm not going to do any of these other ones, but I do want to fix our incorrect price filter. So what we can do is we can go inside of products, procedures, and I think an easy way to fix this would simply to be to spread this. Right? I feel like now there's no way that they won't be combined. Right? We are, of course, going to test this in the next chapter. But I think this should fix it. So I'm just going to go ahead and while I'm in the same branch, I'm going to do a commit 12 filtering products fix. My apologies, git add and then commit and then just push the change. So now when I refresh here, we should have a new commit filtering products fix. So I'm going to show you what our reviewer says about that, and then we're just going to merge the request. I actually think that I accidentally introduced a new bug here because where.price does not exist initially. This will definitely break the code. I would rather we do exactly what CodeRabbit suggested here. Let's do that instead. so i'm just going to check if inside of the input maximum price if we have where dot price let's do it like this so i'm not going to spread this if we have where dot price in that case where price less equal maybe i have to do it like this okay this does not work okay i'm not sure how exactly we should fix this because less than equal should be a part of this but here's what we can do we can do this for So now const, actually where.price can be equal an empty object here. Right, or we can just put price here to be this. And then we will always be able to spread where.price. I'm going to go ahead and test this quickly if it just breaks the app or not. So it seems to be working correctly. like nothing is breaking but i don't know the implications of price being an empty object so here's what i'm going to do instead i'm going to check if we have input minimum price and input maximum price so if both are passed in that case i'm just going to assign both values like this right greater than equal will have input minimum price and less than equal will have a maximum price and then i'm going to do else if input minimum price only then i'm going to do where dot price greater than or equal is input minimum price and i'm going to do another else if here if we have input maximum price only and we can just copy this and remove the spread yes I overcomplicated this for no reason at all. Sometimes the brute force solutions are the best. I mean, the previously one obviously worked, but I don't know what happens if price is just an empty object. This seems to not break anything. It seems to be working. So I'm going to go ahead and add another third commit here. So add everything, git commit, 12, filtering products, fix 2, and git push. I'm not going to do any changes for this chapter anymore. So let me go ahead and go back here. I will just refresh. And I'm just going to wait for filtering products 2 to finish reviewed. And then I'm going to merge it. And there we go. So after this second fix, we can see another comment from CodeRavit. I mean, I can see, right? And we have a confirmation that the implementation now properly handles all three cases, both filters, only minimum price and only maximum price. This correctly fixes the issue identified in the previous review where both filters would overwrite each other. It goes further and it tells me that we should enforce with parse floats, but I think we have enough filters at the moment so i'm gonna go ahead and merge this i am satisfied with these changes i'm not going to delete the branch instead i will just confirm that we have it there we go 12 filtering products and now what i'm gonna do is get checkout back to my main or master and get uh full origin master or main depending on what you use get status for confirmation and a graph check so i have one two three commits here these last two are fixing the issue and then a merge back to the master branch ensure that you're on the master so you can continue with the next chapter there we go and that's it pushed to the github amazing job and see you in the next chapter. In this chapter, we're going to go ahead and connect our previously made filters to the API. We are also going to introduce one more filter and we're going to add sorting, which is going to look something like this, just so you have the UI visual. Let's start by connecting our previously made filters to the API this time. As always, ensure that you are on your master or main branch and you can do git status to confirm everything is up to date go ahead and do bun run div and we have to add our filters in two places right because we do prefetching and what we prefetch must exactly match what we suspense in the client component so i'm going to go ahead inside of my app folder, app route group home category page. The reason I'm going to go here is so I can access the product list. The product list is inside of modules products, UI components, product list. And now what I want to do is I want to pass the filters that I add here, like this, into this query options. so what i'm going to do is i'm simply going to extract the filters from use product filters like this and then what i will be able to do is just spread the filters so right now you can see that adding a thousand to the minimum price uh removes this right so let me just go ahead and remove that filter from the url but you can see that it already works right so if i go ahead and add i don't know 50 nothing changes but if i go 100 still nothing changes but 101 and they no longer exist for the minimum price right but there is an issue with that the issue is that we are not prefetching this correctly right so at the moment this is only happening here, but it should also be happening here in the prefetching. So how do we access those params? Well, we can do that using search params, right? That's one way of doing it, but it would be nice if we could just infer the types from this use product filters from nooks. And that's exactly what we are going to do. So go inside of use product filters here, and you're going to modify this. So instead of parse as string coming from Nux, we are going to import this from Nux server like this. And then we're going to export const params here. And I'm literally going to copy these and put them inside like this. And then let just use the params instead of this like that So that now solved And let export const load product filters create loader from NUCs params So now we have a loader, which we should import from NUCs server here. And now we can use this in this page right here. So the first thing I'm going to do is go back here and I will import type search params from nooks and you can go nooks or nooks server. It looks like it exists in both of them. And now I'm going to go ahead and add search params here and give it a promise search params like this so now i've typed them and then what i'm going to do is obtain all of them using await load product filters from modules products hooks use product filters and i will just pass in search for rams and let's go ahead and json stringify the filters this is from rsc so i have set a minimum price here and i will do a hard refresh and let's go ahead and check it so looks like it does render this is from rsc but it renders null here but as you can see previously it renders with the correct value and i think i experienced this in my original build too but it didn't cause any issue it worked correctly in the end so just confirm that you can load uh your values if you want to you don't even have to use this you can simply do promise and then pass in minimum price and this see i'm not even sure what it should be like string or undefined for example and maximum price max price string or on the phone you can do it like this as well and then you don't have to use it but then you're going to have to add minimum price and maximum price like this and await search params uh like this so then if you want you can add max price and a minimum price like this if you want to you can do that right if it's simpler for you you don't have to use load product filters and changing this to nux server should not really change anything for you i think this should just work normally now uh yeah and let me go ahead and try doing a console log min price and max price rsc let's do a refresh let's scroll down And you can see that it always somehow gets to the undefined, even though in this example it has it. So whichever method you want to use, you can do it like this, or you can use the filters if you don't feel like typing them one by one. And then I'm just going to go ahead and spread the filters in here like this. There we go. So this should work just fine. I believe the errors only appeared because of hot reload. Great. There we go. So now what we have to do next is implement some new filters. But before we do that, I want to improve this clear button. let's go inside of product filters component so right now the clear button i believe does absolutely nothing so let's go ahead and improve that i'm first going to go ahead and do const on clear and i will do set filters whoops set filters and i will set the minimum price to this maximum price to this like this and let's just pass the unclear here like this so when you click it it should clear everything let me just see the issues here yeah so if it says cannot use a session that has ended i think that's hot reload happening it was in the middle of session and then uh i think that we we triggered the hot reload right so i'm gonna go ahead and just quickly google to confirm that that's what it is all right so again looks like it's related to mongodb so but i think it only happens when you hit that hot reload probably the payload connection somehow gets interrupted right but the main point is nothing that should happen in production and nothing that we cause with our code great so we now have a way to reset the filters but it would be nice to only show the clear button if we actually have some filters so let's do const has any filters object entries filters some uh yeah let's wrap this in parenthesis e and value like this and let's just do if type of value is string return if value is not equal to an empty string otherwise if value is not null like this and now we can wrap this button here inside of has any filters looks like my key is never defined here okay so can i just do this yeah for now we're not using the key so it's fine there we go so it won't appear unless i actually type something and then i can clear perfect and i also just want to give this a cursor pointer because i just feel better when I can click on it like this. Great. Now let's go ahead and let's implement a new collection called tag, right? Because we just successfully connected our filters to the API. Now let's go ahead and let's create the tags collection, which is going to be fairly simple. So inside of your collections, go ahead and create tags.ts. You can copy media and paste it inside of the tags because it's going to be very, very similar. So make sure you import this, rename this to tags the slug will be tags the axis you can just read it like this for example and inside of fields the name will be the name of the tag and it will be required and it's just going to have a relation to the products right so type relationship relation to products and has many will be set to true. And upload, remove upload, of course. There we go. You don't need to specify the access in any way, actually. And let me just fix this. It's bothering me. Okay. So after you've defined the tags, make sure you add it inside of your payload config here. So tags, whoops, tags from collections tags. and now we're going to go ahead back inside of the products collection and now we have to add it to the products so i'm going to go after a category here and i'm going to copy it name here will be tags relationship relation to tags and has many true if you have an error here it means that your types have not yet inferred so you can always go and run this to force the types to appear so now you should have tags right so if you go ahead and log in and go inside of your admin dashboard now let's go inside of the dashboard there we go you should now have access to tags so let's just create a few tags here so i'm just going to set this tag to be called i don't know uh really don't know uh test let's just do foo let's go ahead and create another one just a couple of them who bar create you know at least a few because we're going to test pagination on them so test they don't have to be associated with any product for now lauren and let's do one more like this so now you can go to a product and if you want to you can give some a tag and you can see we have an issue here because it's loading them as ids so let's quickly go back inside of our tags here and let's add admin use as title name when you refresh you should see the names actually so for example i'm going to set the loren to one of these products great so we added the new tags collection and we have added tags to the products but now we have to create the tags procedure and add infinite load which is easier than you think so let's go inside of source modules and let's create tags here and i'm just going to go ahead and copy server from products and paste it here so we have the procedures here i'm going to rename this to tags router and I'm going to leave get many as it is, but it's going to be much, much simpler than that. So your input here will have a Z object with a cursor, which is Z dot number and default one. And besides that, it will have a limit, which is Z dot number with a default of 10 like this. And we're going to remove all of this here. Everything is too complicated for this case. And we can remove these imports from here as well. And instead what we're going to do is we're going to search for tags. And we're going to add page input cursor. And.\nLimit, input, limit. That's it. That's pagination. That's what we need in the API for the pagination. One thing I want to do is just change the default limit to a constant. So what I'm going to do is I'm just going to go inside of source and create a new file here, constants.ds, and export const default limit and set it to 10 like this, or maybe 8. whatever you want and change this to default limit from constants there we go so now we have the tags router and now we have to go inside of trpc routers underscore app and we have to add the tags here to tags router so let's just go ahead and add tags router there we go and now we have to go ahead and actually load them and we already have you know a couple of things prepared for us especially inside of this filters. So let's go ahead inside of product filters right here. And what we can do is we can easily copy and paste the product filter below and give this one, instead of price, tags and remove the border bottom zero from the one above. And now you should have tags here, which as of the moment, repeat the exact same thing. so now let's go to use product filters here and let's go ahead and add tags which will be parse as array off and you can import this from nux server as well parse are as array off ours as string with options clear on default set to true so now we have the tags here as well so inside of our new product filter for tags i'm going to add tags filter here and we can go ahead and already give it some props which is going to need so the value in the future will be filters.tags which we now have and on change we'll get the value and call on change tags and pass in the value as simple as that that's the power of this reusable thing we've created here now let's go ahead and let's create the tags filter in the same place we created the price filter which is products ui components so let's create tags filter dot ts we're going to start by creating the interface tags filter props like this and then let's go ahead and let's create the actual component here so make sure you use the props and in here we're heavily going to rely on the checkbox component so make sure you have the checkbox component and let's go ahead and do a div here make sure you call this dot psx so you have the proper syntax inside let's give this a class name of flex flex column and gap y of two like this so what i want to do here is i want to add trpc use trpc from trpc client and in here i want to add the data from use infinite query from 10 stack react query so this is the first time we are doing some infinite querying here and inside of here i'm just going to add a trpc dot tags dot get many and now inside of here we have to pass in the query options so i'm going to go ahead and pass the limit and it's going to be the default limit from constants like this but i think that i might need to use infinite query options there we go so this let me see how this is supposed to look like so i think that it should have two sets of options like this one is this and then the second one if i'm correct we're going to see now get next page barab that's it so we get the last page and we simply return last page docs length is larger than zero then let's get last page next page or undefined so it's that easy because we have built-in pagination in trps in payload yes you might be wondering how come we have the infinite query options infinite query options i don't know if this is still the case. But I think that if I go inside of tags procedures, and if I remove cursor from here, you can see that you can no longer use infinite query options. It doesn't even recommend you to do that. So you need to have a type of cursor in your procedure input for that procedure to unlock, let's say unlock infinite query options. There we go. Great. So now we have the data, we can also extract is loading from here fetch next page as next page and is fetching uh next page so we have all of these options here now let me just prettify this a bit there we go now let's go ahead and use them so first thing i'm going to do is i'm going to check if we are loading so if is loading in that case div and a loader icon from lucid react with a class name size 4 and animate spin and a class name flex items center justify center and padding 4 and if we are not loading we're just going to do data question mark pages dot map get the page like this and then we're going to do page docs map and get the tag and then a div here the div will have a key of tag.id a class name of flex items center justify between and cursor pointer and we're going to have an onclick here for now an empty arrow function inside a paragraph with the name of the tag and the class name of font medium and below that a checkbox component which we have imported and we're just going to give it a check of false and unchecked change an empty arrow function like this and outside of this we're going to check if has next page and if we have next page we're just going to go ahead and render a native button element load more and disabled will be is fetching next page on click we'll call fetch next page and class name will be underline font medium justify start text start and disabled opacity 50. So now let's go ahead and order this like that. Let's go inside of our products filter. Let's import tags filter. There we go. And now let's implement the proper on changes here. So I'm going to do const on click here. Tag will be string. If value includes tag on change, value filter, tag must not equal tag or an empty array. else on change go ahead and simply spread whoops value or an empty array and add the tag at the end i did this incorrectly so the this ends here there we go so basically if the tag is already in the list we remove it from the list otherwise we added to the list that's what we are doing so now let's go ahead and let's actually assign these here so i'm gonna go here and i'm going to do on click tag.name tag.name here the check will be if value question mark includes tag.name and let's do on click tag.name here like this and one thing that we should also do inside of tags collections here is that the name is unique so we want to make sure that each tag can only have one unique name so in case that breaks your app because you have two of the same I think you can just go inside of payload inside of tags and just remove if you have some duplicates here, but if not, you can always reset and seed your database. So now we can safely use name here as the identifier. So if I go ahead here, you can see how it's loading now, and we have some issues here. I think, again, this is MongoDB. There we go. So no problems. you can see how nicely it loads them. And if you want to, you can try this. Change the default limit to two. And now I think that it should load them. Let me just see the error here. Okay, that's something else. Now you can see it offers you to load more. And let me just go ahead inside of the tags filter here. I just want to add cursor pointer here. There we go. Load more. And you can see how it loads more until it reaches the end. Perfect. And when you click here, you can see how it adds them, right? And you can see how they stay selected. And you can clear them. Oh yeah you can clear them yet because in the product filters we forgot to reset the tags to an empty array So when you clear them now there we go Now it works Great. So I'm just gonna go ahead and remove them from the URL. Yeah, it looks like I have to click twice on the first one to get it on, but okay for now. And let's bring this back to eight. Okay, so what we have to do now is we have to connect this to the API. So let's go back inside of products, procedures, and just like we did for the input, min price, and maximum price, we now have to do it for the tags. so tags should be a type of array and string inside and of course nullable and optional and once you confirm that you have them we're just going to go ahead after category let's add them last here we're going to do if input dot tags and input tags length is larger than zero in that case we're going to do where tags dot name in input dot tags just like that so i remember that i have added to one product a tag i think so let me see maybe the this one with the category i think it should have the tag of lorem there we go so if i let me just clear the every clear my filters so they are loading so if i click on lorem only one appears but if i remove both of them appear but if i select some other ones in that case none of them appear so i would say that these filters are working as expected now just a reminder if you go inside of your page category if you manually are writing your filters remember to now add the tags right and remember to add them here in the prefix so mine is automatically inferring by using the create loader here i still think this is the correct way to do it at least so i might actually do one change here after reading a bit about server side usage here you can see that every time they use nux server they separate it in a file so they can create well this is an example for cache right but just here above you can see the example for the loader. So I think we should do the same. Let's go ahead and do this. So instead of hooks here, I'm going to go ahead and create search params.ts like this. And I'm going to import these inside. And what I'm going to do is I'm just going to export const params from here like this. and then I'm going to export load product filters like that and then I'm going to remove load product filters from my use product filters and now I'm just going to bring these back to my NUCs like this so at the moment I'm not going to share between the two even though I think you can share these params i think you can do it no problem but i'm just not entirely sure right so yeah i'm going to do it like this for now i'm not going to share them yet but i'm going to confirm by the next chapter or maybe in some future chapter if we can do this or not so i just want to right now make sure that my used product filters has no imports from the server and then inside of here my search params as you can see has the identical thing but using nux server and it creates a loader so now i have to go back inside of my category page here and i have to import load product filters from products search params but i want to rename this i don't want to use this name i want to use search dash params there we go and you can update the import for this which is right here search params there we go so i think nothing should change now but i feel better having them separated in their own you know server case and here i am in my you know client case i kind of feel more confident this way that nothing will leak accidentally you know just a precaution from my side possibly unneeded but a precaution nonetheless great so what i want to do to wrap up this chapter is just add some sorting here because i think it's just very very easy to do so let's go inside of our app app route group home category page and just before we start this grid i'm going to add a div here and a class name flex flex column button large flex row on the large items center gap y2 on large gap y0 and justify between and then in here i'm going to add a paragraph curated for you with a class name of text to excel and font medium and then in here just another paragraph sorting just so you can see how it looks like let me refresh there we go curated for you and then sorting i think this is the hydration issue that's fine we're going to resolve that later so here it is for you and sorting let's go ahead and let's create the product sort here so in order to do the product sort i'm going to add a new hook first so let me go ahead and close this source and i'm going to go inside of my modules you know maybe i can actually share this with my filters so let me try doing that instead of modules products hooks use product filters i'm gonna go ahead and create the sort values here so const sort values are gonna be uh let's do newest and oldest and default as const like this you can of course use whatever values you want and now let's go ahead and add sort parse as string literal sort values with default curated uh newest or default i don't know I mean, if you want to, you can do it like this. Let's do it like this. So curated. So it looks better. Curated, trending, and hot and new. Like this. And let's pass curated by default. So that's for sort here. And now I immediately want to go ahead and do that in the search params here so I don't forget it. So I'm just going to copy it here. and parse as string literal here from Nux server. There we go. So just make sure you have the same thing in your use product filters and your search params like that. Now let's go inside of our modules and inside of our products here, UI components, and let's create product sort TSX like this. let's export const product sort let's go ahead and get the filters set filters from use product filters like this let's go ahead and let's return a div with a class name flex items center and gap of two let's use a button from components ui button like this and let's give it a size of small and then a class name using cnlib utils rounded full background white cover background white So we override any effects. And then if filters.sort is not curated, let me just see. Filters.sort is not equal to curated. In that case, we're going to render background transparent, border transparent, hover border border, and hover BG transparent like this. now let's go ahead and give it a variant of secondary on click set filters sort curated and let's add a text here curated now let's go ahead and copy and paste this and replace the curated value with trending and trending here let's copy it one more time and this one can be hot and new and hot and new like this there we go so now let's go ahead and just mark this as use client here let's go ahead back inside of our page with the category and let's go ahead and add the product sort component which is a self-closing tag imported from the modules. So when you refresh, hopefully you should have a nice looking picker here and you can see how you can change and you should be able to see the values change in the top corner as well. You can see sort is trending, but when you click on curated, it should clear it completely Great And I also want these values of mine to clear So here what i going to do i going to go ahead inside of my use product filters here and i going to add with default here to be an empty string and i'm going to copy it for the maximum price as well and here i'm going to give it with default like this so we can copy this and go inside of search params and just paste it here because we use the equivalent imports but from the server and i think that now you will have a much better experience because if i type something let me let me try i will remove so this is my current url you can see how it looks weird because i have these empty ones here so now if i go ahead and add 500 it looks like this but if i remove 500 my url now looks like this which is what i expected so now i'm going to try and just add some filters and if i click clear it removes all of them excellent and you can see our filters here are working as well but we just have to connect them to the api but before we can do that you can see that now the clear is always active because in the product filters has any filters doesn't take care of that so let's go ahead and extract the key from here and let's do if key is equal sort we can return false let me refresh okay maybe not this should be okay and let's do let's also do this if array is array return value length so this is for tags so you need to have all of these for this to properly work so now sort will not cause this to show and you can properly wait until these are selected for this to be cleared. Great. And now you can go ahead and connect it to the API. So what I'm going to do is I'm going to go inside of search params and I will export sort values from here simply because this is my server side, right? Search dash params. And then I'm going to go inside of products, procedures. Basically, I'm going into my get many where we do all the filtering and now i'm going to add sort z dot enum sort values and i will import them from search params because i'm certain these is what this is what i use for the server right and now i'm just going to add nullable and optional here as well and now we're going to go ahead and do the sorting for this as well so what i'm going to do is let sort to be a type of sort from payload so make sure you import that that type and by default i'm going to set it to minus created at and then if input sort is equal to trending for example you can go ahead and do whatever you want here so i'm just going to leave them to be all minus created at and then i'm going to i'm going to revisit this later right and let me just go ahead and do trending so if you want to you can reverse some of these if you click on trending try adding a plus here so it at least it goes in the different direction and then before trending you can sort by name and make sure that you add the sort to the final here so now you can try you know let's see so if i click on trending you can see sub category is first and category is last and then if i click on hot and new it gets reversed you can see right so basically that's how sorting works and as you can see it works quite easy and of course you can improve this so since uh yeah this should be curated since this is my default i'm going to leave that to be by newest created and then in here we can for example do name or maybe plus created at right so you can i'm going to come back to this and maybe give you some better options but i just want to give you an idea of how this works right uh great that's it we've added sorting ui and we connected sorting to the API let's go ahead and push all of these amazing changes that we did and I think that we don't have to modify anything in here because we entirely rely on load product filters for the prefetch here right so in here they it matches exactly and inside of my product list here I also don't have to worry because this matches as well I'm going to explore if we can reuse these two because it's an exact copy right now but for now i'm satisfied so this is 13 api filters and sorting so let's do git checkout b 13 api filters sorting get add hit commit 13 api filters and sorting git push u origin 13 api filters and sorting you should now notice that you are on a new branch so let's go inside of our github here let's open a pull request and let's see what our reviewer has to say so i'm going to go ahead and merge this pull request and then i'm going to go ahead and ensure that i pull my changes back into master uh let's go ahead and do git status like this first and then git checkout master or main and then git pull origin master like this so all of those changes are ready and let's do git status there we go so you can now check out the graph we detached to check out the api filters and sorting and we merged the pull request back and i forgot to give you the overview by code rabbit here uh i immediately merged it so i thought that it would not trigger uh but it still did it after i merged it right but it did caution me that the pull request is closed but as you can see we have enhanced product filtering and sorting options with new tag-based filters and sorting criteria which is exactly what we did as always step-by-step change summary here and some more advanced sequence diagrams even the ones for use infinite query hook to load new tags inside of our product filter right here there we go i believe that is it So I have already merged my brand. You can go ahead and do so. I'm satisfied with what we've got here. And now let's go ahead and just mark this as complete. There we go. Amazing job. And see you in the next chapter. In this chapter, we're going to go ahead and continue developing our products list by giving it a proper UI, as you can see here. In order to do that, we're going to have to create a reusable product list view component. We're going to have to create a product card component and loading skeletons, as well as the infinite load for products. Let's go ahead and do that. As always, ensure that you are on your main or master branch and that you have merged all the changes from before. After that, you can go ahead and do bun run dev. What I want to do now is I want to go inside of modules, products, UI, and then inside of components. And in here, I'm going to create a product card component. Inside of here, I want you to have an interface like this. An ID, name, an optional image URL, author username, author image URL, review rating, review count, and the price. So image URL and author image URL should have a question mark and or null type. So now you can go ahead and export a product card from here, along with extracting all the props that you have added right there. So now I'm going to add a link from next link. So make sure you add this import. And for now, the href will just go to an empty page. Below that, add a div with a class name of border, rounded medium, background white, overflow, hidden. And let's do height full, flex, and flex column. now inside of here go ahead and add a div with a relative and aspect square class name and what we're going to render inside will be the actual image of our product so give it an out of name give it a fill property and a class name of object cover and for the source we're going to use the image URL, but image URL could potentially not exist. So we are going to fall back to an empty string. Make sure you have added these two imports. Now let's go ahead inside of the product list component. And instead of rendering this, we're going to go ahead and render the product card component. We can't really fill it with all the props we need, but we will do our best to make it look okay. So let's pass in the product ID as the key, the ID as product.id, name as product.name, image URL will be product.image. And here a catch right So if you go inside of your products collection you can see that we have added a image type upload with a relation to media right So media is an object which has an alt and a source. But you can see that in here, it doesn't allow me to do not source. It has the URL property. It doesn't allow me to do dot URL. The reason for that is the nature of get many here. So if you go inside of the products router where we do get many, where we do all the sorting and everything here, we should modify our products here just a bit so they have the proper... So you can see that I wrote here, this will populate the category and it will populate the image so i uh we already covered the depth doesn't infer the types so what we have to do is we have to modify the get many method so that it properly assigns uh the type of image so what i'm going to do here is i'm going to spread data and add docs data docs dot map get the individual document here spread the individual document and then get the image and we're going to do document that image as media from payload types so the same thing we did as type of category here we are now doing as media here but since this can potentially not exist it can also be null and now when you do the product list here you can see that in here you will be able to do question mark dot url right now you have no more of those errors right but if you put the depth to zero you will get most definitely an error because this will then be a string right so we have to take care and make sure that this is depth one great so now we have the product card here let's go ahead and add some more elements to it so besides the image url we need the author username which for now i'm going to hard code for example antonio uh it will be lowercase most likely uh i'm going to leave out author image url to be undefined but i'm going to keep it here just so we know it exists review rating here will be three and review count will be five so just some dummy numbers for now and price is something we have so we can pass it here let's quickly go back inside of product card and let's just fix this so we actually return this there we go so now you should no longer have any errors inside of your product list so let's go ahead and go to localhost 3000 so we can actually see this in place and what should really happen here is only when you click on business and money right so click on the parent category here and what you should see are two of your products which look completely broken because there is no image added so what you can do here is you can either go ahead and upload an image or you can use a placeholder dot png so where do you get this image this can be whatever you want for example you can choose alf bg if you want to or you can simply use the link in the description where i put all of my assets and just drag and drop placeholder.png like this or if you have access to the source code just go to the public folder and get it from there now let's go inside of product card and add placeholder.png and if you refresh now there we go you can see how now it has a placeholder great so the next thing i want to do is i want to go ahead outside of this div and add a new div with a class name padding 4 border y flex flex column gap 3 and flex 1 Then I want to add an h2 element, which will render the product name and give it a class name of text large, font medium, and line clamp 4. There we go. Now, let's go ahead below this heading and add a div with a class name of flex items center and gap of two. And in here, I'm going to go ahead and do the following. If we have author image URL, only then go ahead and render that image. So I'm going to pass an alt to be author username here. And source will simply be author image URL. Like this. Width will be 16. And same thing for the height. and we can put a class name here of rounded full border shrink zero and size of 16 pixels there we go and then next to that outside of the ternary add a paragraph author username so we are only going to render the image if we have it give this a text small underline and font medium and i'm going to go ahead and give this an unclick here with an empty arrow function for now and add a to do redirect to a user shop so now you should see the name of the user that created this which in our case is just a hard-coded name because we don't associate products with any shops at the moment. So now what I want you to do is go outside of this div and check if review count is larger than zero. So if this has some reviews, we're going to go ahead and give this a flex items center and the gap of one and a star icon. Give a star icon a class name size 3.5 and fill black. I have imported the star icon from Lucid React as always. And below the star icon, add a paragraph which will render the rating and then in parentheses, render the actual review count. And let's give this a text small and font medium. There we go. So you can now see the rating and how many people have rated which is again something we don't have just yet but we will have later and you we will also be able to do floats like this but i think it actually parses it as a round number so yeah let's leave it like this for now now let's go outside of this div and add the last div here with a class name padding4. And let's give it a class name relative. PX2, PY1, border. Background pink 400. Width of fit, a paragraph which will render the price. And a class name text small and font medium. And there we go. might be a safer thing to do this instead instead of just appending that we can do new intl.numberformat and dash us style currency currency us dollars dot format and then parse as number the price there we go and if you want to i think you can also remove maximum fraction digits and i think you can put zero here and then it won't display the digits if you want to you can display the digits so i'm going to put zero here there we go now let's go ahead and let's create the product card skeleton just below this which is extremely simple so export const product card skeleton a full width aspect three quarters background neutral 200 rounded large and animate pulse that's it let's see what the errors are about so the only error here is for the id that's because we are not using it here so for now let's add an imaginary route which does not exist yet which will be slash products id And now you should have no errors at all inside of here. And this should look nice. Now let's add a quick hover effect to this so it matches our buttons. So for that, I'm gonna go ahead and use this hover effect. So let me add it to the beginning actually so you can see better. So hover shadow dash four pixels underscore four pixels underscore zero pixels underscore zero pixels and then underscore and the black color in rgba format make sure you put all of that in curly brackets and inside of curly brackets you must not have any spaces that's why we use the underscore and also go ahead and add a transition shadow and now when you hover there we go you have a similar effect as our buttons but it doesn't actually move too much right i'm noticing some mistake here in our dropdown. I'm going to address that later. But for now, let's go ahead and make this reusable so we can easily put that. Oh, it looks like it's now fixed. I will explore how exactly this happens and why. Interesting. Okay. Now what I want to do is enable this entire look on, for example, a subcategory. So in order to do that we need to ensure that our product list alongside is actually reusable right so right now we have i think let me see uh categories i don't have a home maybe do i have any views i don't so i think i have to go inside of app folder app route route home inside of category page so this is where I have this view. So we are now going to modify this so it's a separate component. So I'm going to copy this entire thing, and I'm going to go inside of my modules, inside of products, inside of UI, and I'm going to create a new folder called views.\nAnd inside of here, I'm going to do product list view dot t s x. I'm going to export const product list view here. And I'm just going to return this entire thing inside. Let's import the sort from components sort, the filters. Let's import suspense from react, the production product list skeleton and product list. so we should have all of them here in a very closed folder because you just have to go inside of components right there we go so we now have this and now we have to fix the props situation so i'm going to go ahead and create an interface props which accepts an optional category let me just fix this into a proper bracket and this into a proper bracket and then in here i will just assign that and extract the category there we go and now let's go ahead and let's use the product list view in the page instead of this so i will remove this and just render the product list view and then we can remove all these individual imports because we now just need the product list view and we no longer need this even there we go so make sure you just added the hydration boundary here around the product list view and remember to pass in the category if it exists and now uh nothing should change if you are on the main category so it should work exactly the same you should be able to sort in reverse you should be able to you know uh sort something out for example like this so there's no results you should still be able to add individual tags here there we go but now we're just going to be able to do that in the subcategory as well so i'm going to go ahead and close this and let's go inside of subcategory so we see the situation happening here and i think that we can just copy what we do in the category here so i'm going to copy the page like this entirely i'm going to go inside of subcategory and i will add the page here. And I will import product list view right here. And I will remove all of these unneeded imports here. And instead of category, I'm going to have subcategory here because that's what we have here. And we just need to assign the props here as well. So let me copy search params from here we can import search params from nooks or nooks server and we need to import load product filters from product search params and instead of category we need to do this and in here we pass subcategory as well and i think that's enough for this to work just as it does on the main category so if i click on yeah it's a bit hard to use when you scroll so if you're having problems with this you can just use this one and then go inside of like this there we go it works just as on our uh category brand uh category page great so we just uh finished creating a reusable component we created a product card component we created a loading skeleton and now we need to do the infinite load. So that's quite easy thanks to payload. Let's go inside of our products procedures in the server. So products server procedures and let's go ahead and just add a cursor z number default one and limit z number default default limit from our constants like this so the same thing we did with the tags right and then we're just going to go ahead where we get the data and pass in the page to the input cursor and limit to the input limit. As simple as this. Now we can go back inside of our product list component, not list view. So our older one, the list component. And in here, we do use suspense query. So now we're going to do use suspense infinite query. and I think I need to import that first from tan stack react query and instead of query options it's going to be infinite query options so that's going to be a set of two options here this is the first one so I'm going to pass the filters like this then the category and then I'm going to do limit default limit from constants and then I'm going to do get next page param here last page and return last page docs length is larger than zero in that case get the next page otherwise undefined there we go that's all we need to implement pagination here and now let's go ahead and just add let's fix this and let's add the button to load more so instead of data.docs we're going to do data.pages.flatmap get the individual page and then return page.docs and that should work exactly the same as it was before and now from here you can also extract as next page for example and you can also extract is fetching next page and fetch next page so basically we need all of these let me just prettify them a bit there we go and now what we're going to do is we're going to go outside of this div so let me see we can yeah we can just do a fragment so we can add two elements inside so just below this div add a new div with a class name flex justify center padding top of eight if has next page and only then we're going to go ahead and render a button from components ui button so just ensure that you have added this as well so components ui button here and add load more give the button a disabled if query is fetching my apologies if is fetching next page on click we'll do fetch next page class name will be font medium disabled opacity 50 text base and background white and variant will be elevated. So I think that now we might not see it at first, but if you change the default limit to one, it will then I think only load one element and you will be able to click load more until it loads both of them there we go so just one more thing i want to do is an option to nicely display if products don't exist so now we're going to do this if data pages is completely empty so data pages first in the array documents that length is zero in that case we're going to go ahead and return a div with a border, border black, border dashed, flex, item center, justify center, padding of eight, flex column, gap Y4, background color of white, full width, and rounded large. Inside, render an inbox icon and a paragraph no products found. Give this a class name of text base and font medium. You can import inbox icon from lucid react so now even if you visit a category which doesn't have anything inside you're going to have a nice display here and let's just wrap it up by creating a nicer loading skeleton so that's going to be pretty easy we're just going to copy this div with the grid, add it here, and then in here, array from length default limit like this. So we are consistent, right? That's how many items we expect to load. And then based on this imaginary new array, we're going to render product card skeleton with a key of index. That's it. product card skeleton from product card. And our product view, you can see already has suspense, which uses the product list skeleton. So if we've done this correctly, you should see nice loading elements now. Excellent. That's it. I think those are all the changes we needed. We added the infinite load. Now let's go ahead and push this to GitHub. So I'm going to go ahead and this is my 14 product list UI. Git checkout B14 product list UI. Git add and git commit. 14 product list UI. Git push U origin 14 product list UI. Once you have confirmed that you are on the new branch and you have pushed that new branch to GitHub, let's go ahead and review our pull request and merge it. So I'm opening a new pull request here and we're going to see what the next steps are. So summary, new features. We launched a refreshed product view that combines detailed product cards showing images, ratings, pricing, and more with integrated sorting and filtering options. We also introduced an infinite scrolling with load more button for seamless product discovery. It also noticed that some of these things were actually refactored As always we can see step by step for each change that we did here And in here we have two sequence diagrams The first sequence diagram actually describes what we do with our prefetching, as you can see here with the load product filters. And down here, it is describing our client-side infinite loading by using the load more button, which requests new products based on the cursor and the limit. It correctly guessed possibly related PRs right here. And it also left some comments which are mostly related to the fact that we removed the default limit, which again, it was just for demonstration. So I'm going to leave it at this. So I'm gonna go ahead and merge this pull request. I'm not going to delete my branch. Instead, I'm just going to confirm that everything is in order. There we go. working product list UI. And that is it. We have pushed to GitHub. And now, as always, let's go back to main or master branch. And make sure that you pull origin. There we go. Git status. You can easily confirm this by seeing the placeholder. That means you successfully merged your new branch. Double check that you are on your master and confirm it here in the graph. Amazing, amazing job. And see you in the next chapter. In this chapter, we're going to go ahead and implement the multi-tenant architecture into our application. But just before we do that, I want to fix two bugs that I've noticed in the last chapter. One is in regards to the drop-down menu position getting lost. And the other one is something I've noticed in my personal code review, which is that we actually do some incorrect prefetching here. And then we're going to discuss how we're going to actually implement multi-tenants here. So as always, ensure that you're on your master branch or your main branch and ensure that you are up to date. After you have confirmed that, go ahead and run bun run dev. So the first thing that we have to fix is our dropdown issue. We never noticed this before because we could never scroll before, right? When I look at it like this, it looks fine. But if I keep this open and scroll, this happens, right? The position gets all messed up. And there's actually a super easy fix. So easy that I'm almost embarrassed, right? but what we actually implemented. Let's go inside of our Home UI components, search filters, categories. And let's go inside of the category dropdown component. In here, we have the use dropdown position hook. We can completely get rid of it. We don't need it at all. And we can remove this, the dropdown position, which also means that we can remove the position prop in the subcategory menu. So now go inside of subcategory menu and remove position from here and remove position from here. Instead of fixed, we will use absolute. And we're just going to keep this right here and keep this like that. And the absolute is important because our category dropdown wrapper needs to be relative, right? So just ensure that you have this div relative because that's where we render the absolute one. And if you try now, it's still not perfect. So instead of this, let's try 100%. There we go. Yes, my apologies. Not zero, 100%. And once you do that, you will be able to scroll while these are open. and there should be no problems whatsoever. So obviously now we beg the question, why did I implement use dropdown in the first place? I think I implemented it because in the beginning, I didn't have the responsive solution that I have now, right? So right now I have the responsive solution here with the view all and my elements, you know, collapse and expand. but I think initially this was scrollable perhaps and then I wanted to do some calculation to make sure it's all visible but looks like CSS is enough for this right you don't even have to put it in style you can put it in here if you want to but yeah that's the first fix let's get that out of the way that was the easy one now let's do the incorrect prefetching so this is also very easy to fix basically inside of our app home category and subcategory we do prefetch query which is not correct because in here we do infinite query when you do infinite query you also need to prefetch the infinite query and now we have to modify this and give it some proper values inside I think that the only thing we actually need to pass here, so I'm going to put the filters first, the category, and then I will just add the limit to be default limit. And it looks like I still need to do something here. So this is a new syntax for me, right? This is the new TRPC integration. So it could be that something else is missing. And I think that instead of query options, I have to use infinite query options. There we go. Now we get rid of the errors. And now we have to do the same thing in the subcategory here. Instead of prefetch query, it is prefetch infinite query. And instead of query options, it is infinite query options. And let's also reverse these two, add a limit to be default limit. There we go. So now we will be prefetching exactly what we will be suspending, right? So if you make a mistake and you don't match what you prefetch and what you suspends, it will simply fall back to client-side fetching, right? It's just going to fall back. It's almost as if you didn't do the prefetch at all. That's how it's going to work. Great. Those are the two issues that I wanted us to resolve. We fixed some bugs. Now let's go ahead and let's add the tenants collection. So why are we adding multi-tenancy now? Well, I don't know if you have noticed, right? But right now, no matter with what account I log in, for example, I think I have, do I have John Doe? John, johndemo.com demo. All right, I do. So let's try this. johndemo.com demo. no matter if i'm logging with john or if i'm logged in with antonio if i go to my admin dashboard and i go into products i can see all products and we want to change that we want each user to become a tenant which in we are internally going to know it's a shop right so let's go ahead and do a new collection called tenants. So each product can belong to a single tenant. And then when a tenant visits the admin dashboard, it will immediately detect that they are a tenant thanks to the plugin, which we are going to add. And it's only going to be see, they will only be able to see the products that they have created. So that's our multi-tenant database structure that we're going to build. so let's copy the users collection let's call this tenants slug will be tenants like this and now let's go ahead and modify it here so use as title here will be slug the fields will be the following so each tenant will have a name which does not have to be unique but it has to be required and let's add a label for this to be store name so that user understands what this is right and the description for the admin when you see admin this basically means for the person who is in the dashboard don't get confused by the word admin in this case in this case in this context what admin means is the person looking at the dashboard so the person looking at the dashboard will see this is the name of the store for example antonio's store this doesn't matter whatever you type here is what we are going to display on your page now the second thing that we need here the second field will be the slug which is a type of text but it's going to be a little bit different it's going to have three properties here index required and unique and that is because this will be the actual subdomain for the store so slug.gumroad.com or in our case funroad.com so this is a made-up website now right uh make sure you put index required and unique i'm going to be calling it slug if you want to you can call it subdomain right i just want to call it slug it kind of makes sense for me that way now what we're going to do is we're going to add a relation to the media so that each store can have like an image, right? And then we're going to add the property Stripe account ID. This will be something that we're going to use to ensure that the person who owns this tenant, which basically translates to the person that owns this store, has verified their details with Stripe. And then we're going to have this field, Stripe Details Submitted, which is a type of checkbox, which is going to be used to give us information whether they have actually successfully passed that verification or not. So when you see checkbox, this basically means a type of field, which is a Boolean. So once this is set to true, we're going to know, OK, this person can now create products. Until this is set to true, we will see that you cannot create products until you submit your Stripe details At the moment we are not having this logic but we will have it later I just want to prepare the fields already So Stripe details submitted Stripe account ID image slug name Those are the fields that we need. Excellent. So now let's go ahead and let's go inside of payload config here and let's add the tenants from collection tenants but just by introducing the tenants not much will be added here you can see that i can now create tenants great right but that isn't what we want what we need now is the multi-tenant plugin so this plugin sets up multi-tenancy for our applications within the admin panel it does so by adding a tenant field to all specified collections. Your frontend application can then query data by tenant. That is exactly what we need. So whenever you see tenant, read store in your mind or a shop in your mind. That's what tenant will be in our application. So in order to do that, we have to install this. And one very important thing, all of your versions for payload need to be the same. You can see the versions that I have here. So be careful when adding. Bon add. And then I'm just going to expand so you can see. Let me see. 3.33.0. That's the version that I need. If you want to, you can use the latest, but then just confirm that you changed all of the other ones to latest as well. Great. Once you have added this, We're going to go ahead and just take a peek to see what we need to do. So we just created our tenants slug. So this is great. Okay, we don't have to modify this, but we are now going to have to add the multi-tenant plugin. So let's go inside of payload.config.ts. So inside of the config, we're now going to add the multi-tenant plugin, which we just installed. and now we're going to add it here in the plugins part. So let's go ahead and add it. And then inside of here, we have to define the collection which will be tied to the tenant. In our case, that's going to be products. So each product will be tied to the tenant. And then I want to add some additional options here. tenants array field include default field will be set to false and one more thing user has access to all tenants so this has to be a boolean right true or false but in our case we're going to go ahead and do the following we're going to get the user from here because it's a function and then we're going to do boolean if user question mark roles includes super admin now you might be confused what are roles here well that's something that we have to do which i forgot right we need to add super admin to use the rules well i didn't actually forget it's the next in line So let's quickly do that as well. We have to go inside of our users collection. And besides the username field, we are now going to go ahead and add the roles field. Like this. name, roles, type, select, default value, user, has many, true, options, super, dash, admin, or just a normal user, like this. And let's do one more thing, admin, position, sidebar. just so we separate it from the other fields in the form. So now it makes more sense, right? Because now our user can have the rules. And my apologies, did I call it roles? There we go. So roles is the name. And I think that we should get the type here or maybe not, maybe it's going to stay at any, but just make sure that you have the roles here and that you have the super admin here as well. Great, so let's see what we've done so far. I think that right now, this won't just magically start working, right? Because we haven't defined any roles, like things are still in some weird state. We're gonna have to refresh our data. But let me just see. So we added the tenants collection, we added multi-tenant plugin, and we have created super admin roles. Now we have to go ahead and add the tenant field to the user. So the way we do that is by using the multi-tenant plugin inside of the user's collection. So let's go ahead and import tenants array field from payload CMS plugin multi-tenant slash fields. So what we're going to do now is the following. const default tenant array field will be tenants array field and then some properties here so tenant tenant array field name will be tenants tenant collection slug will be tenants which needs to match the slug inside of our tenants collection. Tenants array tenant field name will be tenant. Array field access for now will be read set to true and we're just going to copy create and update set to true. For now later on this won't be true and we can copy these and change them to tenant field access and keep them all to true as well just for now obviously this is just so we can test this easily and now let's go ahead and spread this field inside of our fields array for the user here so i'm just going to open a new field and we just spread the field above and add some additional things like admin let's copy whatever we might have inside of the default tenant array field question mark dot admin which might be an empty object and position sidebar so now we have attached the tenant to be controlled by the user collection but the tenant is also in close relation with products because of the collection here my ball is the config right here so i think that still not much should change here but we are going to go ahead and start improving this now so we have added the tenant field and now we have to create the tenant on user registration so let's go ahead inside of source inside of modules out server procedures and let's find register. And now once we create the user, we also have to create the tenant and connect those two. So let's go ahead and do the following. Let me just see. So we first check if this exists. So only here should we do that. const tenant will be await context.database.create. Collection will be tenants. Again, if you have TypeScript errors here, meaning that tenants are not an option here, you most likely have to manually run generate types. So you have updated types. Now let's add some data here. The name will simply be input username. So whatever user entered in the registration form for the username and the slug will be the same because the username has to be unique. The name is something that user will be able to change later. Right. So they can rename it to Antonio's store instead of just Antonio. Right. And let's add Stripe account ID for now. Mock. Let's just do that or test. and i seem to be having an error here let me quickly go inside of my tenants collection to see that i didn't accidentally do something incorrectly this is required this is not required okay but let me see uh email is declared here in the tenants that is odd i'm going to check if that is correct or not so i don't know why it's expecting email here my my version did not need email but let's see what if i do input.email because that has to be unique as well it could be just an updated package right because i'm definitely using a newer version than what i developed with initially so maybe they've changed it so that you need an email here as well which would make sense if it is going to be connected to the user oh because of this let's go inside of the tenants collection and remove out right the outfield snuck in here because we've copied from users so just do a quick check make sure we didn't do that mistake somewhere else because that would be bad looks like we didn't okay so now you can see that it doesn't need the email if yours still needs the email run this so make sure your tenants collection doesn't have the outfield. Excellent. Now that we have this, let's go ahead and connect this tenant to the new user right here. So I'm just going to go ahead and add tenants. And it has to be an array because this plugin actually enables individual users to own multiple stores That not going to be the case for our product are going to limit one store per tenant just for simplicity sake but later on you going to have no problems configuring this further and allowing multiple stores and that's actually it this is the entire logic you need to create multi-tenant architecture so this is what we are going to do now we're going to go inside of our seed script right here and what we're going to do is this is where we're going to create the super admin the reason i want to do it from the seed script is because even if you were to run database reset you would get prompted to create an account on the payload admin page but from their registration, I'm not sure how to exactly create the tenant, right? So instead, what we're going to do is the following. Sorry, not how to create a tenant. The admin doesn't need a tenant, but I'm not sure how to give the proper role of super admin. That's what I don't understand. So instead I'm going to do that in the seed script so create admin user await payload create collection users data email admindemo.com password demo roles super admin username admin that's it that's all we need so let's go ahead and let's reset our database i'm going to shut down the app and do bond run database reset we are going to get prompted asking if we want to delete all data you can safely press yes when it appears there we go and then it's doing database run seed, which means adding the categories and this time also adding the admin. So now go ahead and do bun run dev. And let's go ahead inside of localhost 3000 here. If you were previously logged in, you will now be logged out because your cookie is invalid. Let's go ahead and click log in here. and you should now be able to go inside of admin at demo.com and demo password. There we go. So you have a role of super admin. And if you go to your dashboard, you are going to see something new. At least you should see something new. It's not visible yet, but if you go inside of your account, so click here, you will see the role of super admin right here. And you can also see that it offers you to connect your account to a tenant, right? But that's not what I wanted to show you. Go ahead and log out. You can use this button here behind the next button. You can click the log out button and go to the normal login page. Go to the register page here and let's create Antonio and Antonio at demo.com and demo. and click create an account. Now, what will be interesting is that you can already see I have a tenants array here and a connected tenant. So now I should be able to create products here which are only connected to my tenant, which I can see right here. Right now, I can also remove myself from a tenant. All of these things will be protected in a better way. This is just for an easier demonstration. So let's go ahead and create a product here. Looks like I'm having an error here. It could be because I have set something up incorrectly. So let me try going back to admin, the products, create new product. Maybe I've set up something incorrectly. I'm going to go ahead and recheck. so i believe i found the exact issue that we got here multi-tenant example cannot destructure property config and then this what seems to be a bundled code and looks pretty much the same as ours and i think what it might be as you can see here in a few comments saying that they've deleted node modules and the lock file and reinstalled the packages. So I guess this could be due to the fact that maybe when I have this little carrot here, they have a slightly different lock file than this one, which is the exact version. So here is what I'm going to do. I'm going to delete my node modules that's the first thing i'm going to do so rm rf node modules and shut down my app of course so node modules deleted and i'm then going to also delete my dot next folder in case there's any cache i'm then going to rerun bun install so i didn't delete the log file i don't think there's the need to delete the the log file and now i will do bond run database reset again right i just want to have a complete fresh database here let's confirm and then run the seed script and looks like we have some error here so maybe this is what's happening uh this is this is probably I think that this is either MongoDB or maybe let me just see what this is write conflict let me just research a bit perhaps we need to modify our seed script um so these are some errors here let me see if i just do a reload window do these errors go away they seem to go away after i reload so what i'm going to do is i'm also going to create create admin tenant here so await so const admin tenant will be await payload create collection tenants name admin whoops i have to put this into data name admin username admin and my apologies slug is admin stripe account id well it can also be admin it really doesn't matter And then in here, I'm going to do tenants. ID tenant admin tenant dot ID. Let me just check if this is correctly created. Admin tenant is a tenant here. And in here, it says that tenant is missing. I have access to the ID here, but it says that this is not enough. Let me see. What am I missing here? I'm sorry. It's tenant. There we go. I should have just looked at my ALF procedure. So basically what I've done now is ensure that even the admin has a tenant just in case. and now i'm just going to try and do this again and maybe do it manually just to make sure maybe it's something with the concurrency of the script let's try manual reset of the database first let's confirm and still the same error so i'm going to go ahead and try and research what this error is okay so i think i figured out what is the issue i thought it was something with the categories so i have commented them out but it's not so everything is completely fine with our code here but i think something's wrong with this script i think database run seed uh runs before this has finished I think that's an issue so I'm not going to be using this script anymore instead I will be running them independently database fresh like this and then after I confirm that this drops the database by confirming and then do bond run database seed let's see there we go seeding completed so i think it was that script uh probably not the correct the way i should create that script i'm going to explore later if there's a way to improve it but as of now i think it's okay like this let's give it another shot now so i'm gonna go to local host 3000 here. And I'm going to go ahead and create a new account, Antonio, Antonio at demo.com and demo. I automatically have a new tenant now. And if we still have the same error, I'm going to try again, but this time I will delete the log file. So I can access the products. And there we go i can now create in your product so look like it was just a fluke after you add the multi-tenant go ahead and do bond run my apologies rmrf node modules and rmrf dot next and then do bond install and then bond run database fresh and then bond run database seed right so that is a solution for our error. At least in my case, I just had to reinstall all packages.\nblock file is now assigned. So I'm going to call this Antonio's product. I'm going to copy the same thing in here, give it a price of 50, select a category and no need for a tag at the moment. And let's click save. So now in my products here, you can see I have Antonio's product. And now I'm going to go ahead and log out here. and I'm going to go here just to see if it loads in business and money here because I think that's the category I put in. There we go, Antonio's product. Now I'm going to go ahead and create JohnDemo.com. My apologies, John, JohnDemo.com and demo for the password. And I'm going to go to the dashboard and you can see that now in my products, I cannot see Antonio's product. I can only see the product that belongs to my tenant. So this is John's product now, 100. And let's set a category to business and money as well. And let's click save. You can see that I can only see John's product when I'm logged in as John, right? But if I go to antoniodemo.com in my products, I can only see Antonio's product. but if I go ahead and use my admin, right? If I use my admin demo.com, I should be able to see all of them. Admin demo.com and demo. There we go. You can see that I can either choose a tenant I want to look at, or I can just look at all of them. So if I click on products, I can see both John's product and Antonio's product. Or I can pretend to be Antonio and then I can only see there. Or I can pretend to be John and then I can only see John's. So that is exactly what I wanted for us to achieve with this multi-tenant structure here. And while we are here, let's also enable the all button because all doesn't do much at the moment. so in order to fill this page i'm just going to go and copy from my category page i can copy the entire thing and now i will go inside of home page here and i will replace it entirely like this the only thing that's going to change is the fact that i don't have the params i still have search params. I just don't have the params, which means that category in this case does not exist. And that should be completely okay, right? We don't need to have a category. If we don't pass a category to get many, we are just not going to query by any specific category in the where. And there we go. You can see that now in all, you can see all products. There we go. So that was what I wanted for us to do you can also go inside of contents and increase the default limit to eight right just so you can just so it makes more sense and if you are logged in as an admin you can also go to admindemo.com demo you should be able you know to modify any product so for example go inside of john's product and change from business and money to i don't know uh let me go to something to web development or software development let's click save and now just to prove that our all filter works so on all they are both loaded but in business and money only one is loaded until i go to software development here web development uh and click on software development here not the best UX, but yeah, okay. At least we can go here. There we go. And we can now see John's product, right? Excellent. So this is a great start. I think we did everything we wanted. We create tenant on registration and we connect products collection to a tenant. So each product now belongs to a tenant, which is essentially a shop. So just to recap, you will most likely get the same error as I did. So remove your node modules and reinstall your packages. Looks like our script for combining fresh and seed doesn't work as expected. So if you get the same error that I did, which was this one, let me try and find it. This is the error. Transient transaction error. Basically, it says that some IDs are already in use, which is, you know, write conflict is almost a telltale that your old data was not deleted. So obviously you first have to run this, wait a couple of seconds, and then go ahead and run this right here. Great, amazing, amazing job. That's it for this chapter. So we're going to go ahead now and create 15 multi-tenancy branch. Get checkout would be 15 multi-tenancy. There we go. Git add, git commit, 15 multi-tenancy. And git push u origin, 15 multi-tenancy. Once you have confirmed you're on your new branch, go inside of your GitHub, open a pull request here, and create a pull request. And here's a summary. We added the multi-tenant support, enabling tenant management, and enhanced role assignments. We also added the new product list view to our homepage, so it detected that as well as the increased default product limit. We updated the registration process to automatically associate new users with a tenant, and we fixed our invalid drop-down filtering menu bug. as always walk through file by file you can see how we removed the database reset script it wasn't unused it was just not working correctly and it also detected the multi-tenancy support by adding a dependency and updating the config the collections the type everything we need along with the seed script. In here, we have a sequence diagram, which talks about how we create a tenant using the username as name and slug. And we return the tenant ID, which we then use to create the new user. And that's how registration works for us. In here, it gave some good comments. We don't need to do this because we already do it using Zod, but we could implement logic error handling if tenant creation fails uh that could be the potential fallback but we don't have to worry about this we already thought of this the tenant can only be the username can only be something that will be used as a domain so i'm gonna go ahead and merge this pull request there we go let me just confirm that i have this branch here i do perfect and now we're gonna go ahead and do a git checkout and make sure you are on your main or master branch and git pull origin main or master and git status there we go and confirm with your graph right here perfect so we can see that we added the new multi-tenant plugin here we can see that we changed the default limit. We can see that we added the tenant. We can see that we added the tenant field to user along with the roles. We can see the new creation of tenant in the registration procedure. We can see that we removed the faulty drop-down position hook. And we can also see the modified seed script. This is the config for multi-tenant and the modified seed script right here. And somewhere in here, it was the packet JSON, but I can't see it now. There we go. So we removed the seed script. Perfect. Confirm that you are on your master branch seeing that. And that's it. We pushed the GitHub. Amazing job. And see you in the next chapter. In this chapter, we're going to go ahead and develop tenant pages. So in the last chapter, we actually implemented the multi-tenant functionality. So right now we have the ability for each tenant to have their own product, right? And when they go to their dashboard, they can only see their own products besides the admin. Admin can see everyone's products, which is exactly what we wanted. So now we're gonna go ahead and extend on that information by starting to load tenant, which basically means shop. Whenever you hear tenant, think of a shop, think of a store on our website, right? So we will load shop information into our product card. Let's start by properly having the shop's name here instead of hard-coded Antonio. As always, ensure that you are on your master branch, ensure that your graph looks correct. you can always run git status to confirm now let's do a bun run dev here and let's go inside of source let's go inside of app here actually i'm going to go inside of modules my apologies modules products ui components let's go inside of the product list because this is where we pass the author username so now what i want to do is i want to ask at past product tenant and then in here actually it's always going to have a tenant in here i want to pass the uh should it be the slug or the name i think we can pass the name but we have some improper types here so let's go ahead inside of getMany and let's see how we can improve that. So right here where I do const data await context database.find, I know that I use depth one, which means that I populate the category and the image. And that now also includes tenant. So tenant will also be populated which means that I can now add tenant doc as tenant from payload types And we also know that the tenant will be required, right? So no need for us to add or null. Tenant is required for every single product that is created. so now you can safely pass product.tenant.name here so if you want to you can do one thing here you can do a console log and json stringify data.tenant actually just do data.docs right and then when you do a refresh on your localhost just make sure that you are running so i think that I'm on my all page in here oh I should have added null two so it's printed out in a prettier way so now in here you should see a tenant there we go but if you change the depth to zero I think that then it looks like it doesn't fail, right? But it definitely doesn't do what it should, as you can see right here. So make sure the depth is at least one, and then you will see that the tenant is properly populated. In case you forget to put depth, I confirmed with the theme from payload, the default depth is actually two. So you don't have to worry. But I like to be explicit. I like to know why I'm adding this. Great. So now you can see that this says John and this says Antonio. Great. Now, if you are aware of the tenant collection structure, you also know that we have an image, which means that we can also populate the author image URL, which is product.tenant. tenant.image, but it's actually .url, same way as this. And that is definitely going to be optional here. And let's also put question marks here just in case, right? I don't want our front end to break. So I'm going to add a question mark.url here, and I'm going to go back inside of my procedures. And I'm just going to extend the tenant to also have an image, which is a type of media or null. So basically the same thing as this. And now it's properly being treated because we know that this will be populated, right? As you can see right now, I don't have any images here, but what I'm going to do is I'm going to go inside of my Antonio tenant here. So Antonio demo.com demo and I'm going to go inside of my dashboard and I'm going to add an image for my tenant right so right now if I click on tenants I can also only see the Antonio tenant I can't see John's tenant so go inside of your Antonio tenant and let's upload an image here so I uploaded an image and I will just call this Antonio in the alt and I will click save and then I will click save again. So I just want to see, does this load the image or not? I'm curious because depth one might not be enough to load something that's inside of something we loaded. Perhaps in this case, we might need depth two. So let me go here and looks like it's not loading. Let's see whether that is correct by taking a look here. looks like it cannot read the properties of url so what we have to do is we have to go inside of here and change the depth to two so this will populate tenant and tenant dot image now now i'm pretty confident it will work and there we go you can see how my image immediately pops up So that's how you have to be careful, right? So when it comes to our get many base procedure in the products router, we want to have depth two. So we populated the category, the image, the tenant, and also the tenant image. This is the depth two part. This is the first level, and this is the second level, right? That's what we wanted to have right here. Excellent. So now ensure that you don't have any errors here. you can see how I had a clear error here, right? It couldn't read that undefined. So it would also be useful for us to, I'm not sure where do I use it like this? Because in here it's written almost as if, you know, I don't add these question marks, but make sure you add these question marks because you can see it can be tricky if you mess up the depth, right? But yeah, by default, this should work. If I just add a comment here, you can see that everything will stay the same because depth two is the default right i'm not sure if it's documented uh but depth two is the default so you don't have to worry but make sure that depth is set to two here excellent so now what i want to do is enable that when we click on antonio i want to be redirected to the actual uh tenant slug right that's what i want to do so let me go ahead and mark this as completed and for in order to do what i just said we need to create the tenant shop page right but before we do that let's first add the tenant slug filter to the products get many feature because that will allow us to query by an individual tenant so i'm going to go back inside of my get many procedures here. And after all of these, I'm just going to add tenant slug, which will be a string and nullable and optional like this. And now let's go ahead and use the tenant slug. So I'm going to do that just before I do the category here. If we have input tenant slug we're just gonna target the where for the tenant dot slug to be equals input tenant slug that's it that's all the code we need to query by a tenant perfect so now that we have the tenant slug we can check this as done and now let's create the tenant shop page so as you know we will have uh you know things like we will have things like uh slug.domain.com but in order to do that in future rewrite to that right so this is how we are going to keep the shop internally and then we're going to use the middleware to rewrite to this so in case you were worried you know but I want this subdomain, don't worry. We will have the subdomain, but we first have to create the internal route. So let's go inside of source app folder here. And let's go and create, inside of the app route group, tenants, like that. And then inside, create tenants without brackets, and then slug, like this. And then inside of here, let's go ahead and create a page.psx like this. And now in here, what we can do is we can create the interface page props, import the search params from Nook's server. And we can just call these props, actually. So basically the same thing that we have in our two category routes, right? and we're also going to import the product list view the load product filters from not from hooks use product filters it's going to be from search params yes that's the correct one we are going to add hydrate client and drpc from at drpc server and we're going to add the default limit constant right and let's change this to import type then let's do a con let's do the default export of this page here let's mark it as an asynchronous page let's destructure the params and search params let's add page props here page uh apologies props and then in here we can destructure the slug from await params and then we can get the filters from await load product filters search params basically the exact same thing that we are doing in a category page right but instead of slug it's category you can see this is the exact same thing that's the power of these reusable things we've created now let's go ahead and let's copy these and let's add that here so we are getting the query clients let me just import this from server i completely forgot about that uh and we don't have hydrate client my apologies we're going to use a hydration boundary yes so this is query client prefetch infinite query and instead of category we will have tenant slug and match that to be slug like that. And then in here, you can just copy this thing like this and import hydration boundary and dehydrate from 10 stack react query. Let me move it here with the imports. And instead of passing the category, we're going to have to introduce a new prop tenant slug to be tenant slug. there we go and now uh this is just slug in this case and now let's go inside of the product list view and let's add tenant slug which is going to be an optional string here and now let's go ahead and use the tenant slug in here so now we have to let's also import it i mean destructure it now let's go inside of the product component and add it here So we are doing some prop drilling but it okay This is reusable and it going to make our lives easier And now I want to pass the tenant slug here There we go So now we can ensure that this page prefetch infinite query 100% matches all the possible combinations in this case. So now we should be able to do the following. You should be able to manually go to localhost 3000 slash tenants and then slash Antonio. So this should load the tenant page. And there we go. You can see that we successfully loaded Antonio's page and it only loads the Antonio's product. If I change my URL to go to John, it will only load John's product. So just like that, we have created these individual shop pages. And now we're just going to create their layout in a bit of a better way. So let's go ahead and do the following. Inside of app folder, app route group tenants, inside of here, I want to go ahead and create a better layout. And I actually want to do this. I want to go in the slug and create another route group called home and then move this page inside because later we're going to have some more route groups here if it asks you to update the imports you can press yes and then you're going to have this weird unsaved file just save it close it and make sure you close the dot next folder so you don't accidentally write something inside and then just safely go back inside of your home route group in the tenants slug here. And inside of the route group, create the layout file, which we are now going to use. So in here, I'm gonna go ahead and create the layout. And I'm going to create a div with a class name, minimum height of screen, a background of F4, F4, F0, flex and flex column like this. and then I'm just going to go ahead and create an interface layout props which will accept the children but also the params which will hold our tenant slug right here so I'm going to extract the layout props here children and params and for now I can just render the children. So if you go ahead to localhost 3000 tenants Antonio, everything should stay the same. Nothing should change for now, except the background color. The background color should be a little bit different. So now what I want to do here is I want to add a navbar component here. We are not yet going to be using these params. You can remove them if you want to. So now we're going to create a very simple navbar component here. So we can do that by going inside of the modules. And let's create tenants. And let's create UI folder. And then components. And then inside, create the navbar.dsx. Let's export const navbar here. And let's return instead of a div, a nav element with a class name height 20, border bottom, font medium, and background color of white. And then in here, let's create a div with a class name maximum width. And this is actually a new Tailwind version for class name, dash dash breakpoint dash Excel. So usually you would do this with maximum width screen LG, something like this. But there we go. You can see how it's warning me now, right? CSS conflict. So this is Tailwind version three. And this is Tailwind version four. I told you we're going to learn something new about Tailwind in this tutorial, even if it took us nine hours to get there. Now let's do MX auto here. flex justify between items center height pull px4 and on large px12 we are basically creating a very simple navbar which is going to just render the word tenant for now and let's give it a class name of text excel like this just a navbar and then you can import this navbar from tenants so make sure you use the tenants navbar and if you refresh here you should see the tenant navbar right here now let's do the same thing but for the footer so i'm going to go ahead and add a footer component here which we are going to build in the tenants ui so copy navbar paste it rename it to footer like this and rename this to footer as well and the only difference here is that i'm going to add the app logo here so i'm importing poppins here and cn and link and then i'm just going to call this poppins and the only weight we actually need is 700 like this let's change the nav element to footer element and let's change this class name to not have any height and instead have a border top font medium and background white can stay and this entire class name can stay as well so we need the same breakpoint and then in here we're going to do a text powered by we can remove all class names and then we're going to do a link with an href to go back to the root page with a span fun road with a class name cn text to excel font semi bold and logo my apologies poppins class name so now you can go inside of the layout and you can import the footer from module stannan's ui components footer refresh and now you can see the footer right here let's go ahead and just quickly improve it so it looks just a little bit better we don't need the justify between here so it should just be powered by fun road like this uh and let's see let's add a gap to here there we go and if you click on fun road you get redirected back to the root page right so on each shop page you will see your your app's name your platform's name and now let's go inside of the layout and let's wrap the children around a div here and give it a class name of flex one and then another div here and in here we're just going to go ahead and add a class name for the breakpoint right so we want to add this maximum width and mx auto so basically we want everything to be a little bit pushed when it comes to the tenant page so there's a clear difference between this page right and then when we go on the individual tenants page and i don't like the footer so let me just go here and let me add besides px let me add py6 wait for the refresh there we go now it looks better great so now we can access individual tenants page i can change my url to john and i can see the john's product now but now wouldn't it be cool if we could load uh the name of the tenant right here and also display their shop's name and display their shop's image. So we can actually do that. In the layout, we can activate a prefetched based on the promise here. In order to do that, we first have to create the tenant router. So let's go inside of modules and let's copy a simple one. For example, the tags procedures. Let's copy them and let's paste them inside of server here. Let's just paste the procedures inside of the tenants modules. And instead of get money, we will have get one. And the only thing we're going to look for is the slug, which will be a string. And the data here will do the following. Tenants, await context database, find collection tenants. And we're going to do where slug equals input.slug. and then we are also going to make sure the limit is one and pagination is set to false and let's do const individual tenant is tenant dot docs my apologies tenants we can call this tenants data so tenants data dot docs and then the first one in the array and we can check if there is no tenant, we can throw new trpc error with a code not found and a message tenant not found. You can import the trpc error from at trpc server from the actual package. Otherwise, just return back the tenant now we have to include the the uh tenants router right or tenant router let me just see products procedures how do i call it products okay so this will be tenants router now let's go inside of the trpc folder inside of the underscore app inside of the routers folder and we are just going to add tenants, tenants router and import it. There we go. So now you should be able to go to your tenants layout right here. Mark this as an asynchronous method. And now we going to do the prefetch We just need to destructure the slug from the params We can now add params here And then just void. And let me just go to a random page so I can see what I need. We need the query client so I can just copy all of this. We need the query client from the RPC, get query client from at trpc server we need trpc from trpc server we are not going to have any filters category or limit we're just going to pass the slug to trpc tenants get one so just pass in the slot and it's going to be query options and just prefetch query there we go so once you've done this you're going to go ahead and wrap the navbar inside of hydration boundary and you're going to pass let me just see the state to dehydrate the query client import that from tan stack query alongside the hydration boundary and now your navbar will have prefetched the actual sorry the actual tenant so what you can do now is you can prepare const erpc from used erpc from client mark these as use client and now you can do const and get the data from use suspense query from 10 stack react query drpc tenants get one query options and in here you should pass the slug so we're going to create interface props slug string and we are going to destructure the slug from here and now you can safely pass the slug there we go and now you can use data as if it was already loaded so you don't have to do any if loads right you can already use it here you can do data dot name the data will 100 percent exist when this component loads you just have to pass in the slug here so now when you refresh the antonio it will say antonio when you go to john it will say john right here and it prefetches that now let's go ahead and create the proper skeleton here so we can load this even faster so i'm going to copy this and export const navbar skeleton i will just pass this and what i'm going to do here is i'm just going to keep this as an empty div because later there will be to do skeleton or checkout button. Right now it doesn't exist. And everything else can stay the same. So you can just use the navbar skeleton here in the layout so you can add a suspense here. Fallback navbar skeleton. Make sure you have imported both of them. so now for a split second nothing should be displayed that's our skeleton you can see how now when i refresh it's in the cache right but if i switch to antonio uh so this is the case where it actually can't find something and it will start throwing errors if we can actually handle this in a different way i'm going to explore the error handling later there are several ways we can do that right but for now just go to a proper slug name antonio here and now i also want to load the image if it exists right so let's go inside of the navbar here and let's load um the user's image so we're gonna do that here let's also add a link next link here and let's give it an href and for now it's gonna go to slash tenants and it's going to go to data actually you can just go to slide like this and give it a class name flex item center and gap two and now in here we're going to do if data image question mark url then we're going to go ahead and render an image from next image with a source of tenant image whoops data image url ignore the typescript errors for now i'm going to explain what's happening in a second let's give a width and height of 32 and a class name rounded full border shrink zero and the size of 32 pixels and an alp can be slug so why does it say that url does not exist we can clearly see that the code works well again it's because of our get one here so by default it uses depth two right this is the default if you want to you can set it to depth one which will mean that tenant dot image is a type of media because it's going to be populated but we've already explained that payload's local API does not change TypeScript depending on the depth. So we're going to have to do this ourselves in this case. So what we can do here is return tenant as tenant from payload types and add media, my apologies, image to be a type of media from payload types or null because it's not required for the user to have an image for the tenant to have an image so import those two and now everything should still work just fine you can see that it works with depth one but if you change to depth zero then it no longer works so in this case dot image is on the first level of depth so depth one or depth two are going to work equally you can choose which one you want to use depth one should work but of course confirm right make sure it works and you can see that now no more errors in this part at all and if you click uh yeah right now it doesn't make sense but this will be used later when we actually implement uh this right this will usually lead to the products page and i want to do uh one more thing so let me see right now when i go i i only use tenants slug whoops i only use tenants slug here so i want to implement one method which we are going to heavily use later on and i already want to prepare for it let's go inside of the utils right so let me get inside of source lib utils and in here let's export function generate tenant url and pass in the tenant slug the string my apologies this is not an arrow function and for now what we're going to do is just return slash tenants and then slot that's the only thing that we're going to do for now right but later this is going to come in handy so let me go inside of my nav bar where i use the where i load the tenant and we're just going to pass in the generate tenant url and pass in the slug so nothing should change right now but later this will help us because of that subdomain thing that i'm pretty sure all of you are eagerly waiting for right so nothing should change now this should still work just fine and now let's also enable that when user clicks on the name of the user in here it also redirects to the shop since the product card itself is already a link we cannot nest another link inside instead we're going to do this right we added to do redirect to user shop So what we can do is we can do handle user click like that. And then we're going to have to stop propagation. So this link doesn't activate. So const handle user click will get the event, which is a type of react.mouse event. HTML div element. Event prevent default. Event stop propagation. And then we're going to have to add const router to be use router from next navigation. And router push generate tenant URL and pass in the author username, which we know is the actual. Oh, yeah, this is important. Actually, this is very important. This would not always work now. So make sure that inside of the product list component, which is located inside of modules, modules products ui components product list where you render the product card for the author username make sure you use product tenant slug instead of name and here's an even better thing let's change this to tenant slug and let's change this to tenant image url let's be specific about what we are using now let's go inside of the product card and let's modify this so tenant slug and tenant image url so we never confuse these two things with what they actually are so now we can use the tenant slug here let's see where else here in the alt and these two are tenant image url\nand in here use tenant slug. And that's it. I think everything else works just fine. And we can now remove the to-do from here. So make sure that you have, I'm not sure if you need the React import because we are only using it for types. But now this should work. If you now click on Antonio, you should get redirected to the Antonio's shop. If you click on John, you should get redirected to John's shop. And this should still work, by the way, right? Every single other filter should definitely work. If I put minimum price 99, it works, but 101 and no products found. If I clear, they're back. So everything should still work. That's the power of all of these reusable components we have created. Amazing, amazing job. So what we can confidently start doing next is building the actual product page. We've created this shop. The only thing I might do in this shop is maybe change the grid a little bit because I've noticed since we do this maximum width restriction, the cards look very narrow. So I might change the grid to allow the cards to take more space than they usually do because this looks completely fine in in here but in here where we do a smaller screen they look a little bit narrow so maybe i'm going to make them a little bit wider we can actually do that in this chapter we are already here let's start with the product list component inside of the product list component add a new narrow view optional boolean component go ahead and destructure it from the props here Once you have the narrow view component we going to go and find the grid here Go ahead and change this from the static CSS to now be CN which is our util class names. You can import CN from at lib utils. Now we can keep this as the default class and add, If we have narrow view, we're going to change it. On large, grid, columns 2. On Excel, grid, columns 3. On 2XL, grid, columns 3. Ensure that all of these are properly written. And that's pretty much it. Now what we have to do is find where we render the product list. There's only one place in the product list view, which means that in here we need to also prop drill the narrow view optional boolean extract it from here and then pass it here like this and then what we have to do is we have to go inside of the tenant page so inside of tenants slug home page and simply pass narrow view to this and already it should look better but now you can see that the skeleton does not match so let's go inside of the product list view here and let's pass narrow view to the skeleton as well let's go inside and edit the product list skeleton narrow view and we can just copy the div so yes we can modify the skeleton in the same way why not We want to make it look good. And now, even the skeleton matches exactly what we are going to see. You can see how now it just all looks much better. Let me just see if this skeleton... Now, this skeleton doesn't seem to be looking too good. So let me just see what this is about For this one yeah this is interesting Why does it look so much bigger I'm not sure. It's because in the product list view component, we hard-coded this instead of passing it as a prop. Whoops, narrow view. There we go. So inside of the views, product list view, make sure that the props for the narrow view are props and now the skeletons will match exactly what they need to be and the user site will actually look good now it looks unique because it cannot be as wide and the products still look good because they have been given enough space now amazing job that's all and even more than what i planned for this chapter so we've created the tenant shop page and we've created this and this is what we're going to do in the future now let's go ahead and push this to github so 16 tenant pages i'm going to go ahead and do a git checkout b 16 tenant pages i will do git add git commit 16 tenant pages and then get push u origin 16 tenant pages. You should now see your branch change in the IDE as well. You should see the detachment from main or master branch. Let's go to GitHub. Let's open a pull request and let's create a pull request. So the summary, new features. We have introduced tenant-specific homepages featuring a refreshed layout with dynamic navigation and a new footer. We have also launched an enhanced product page that filters and displays items based on tenant context, adapting the view for different layouts. Speaking of the narrow view we also added seamless URL generation and improved backend endpoints for retrieving the tenant data ensuring a smoother overall experience As always, you can go ahead and read through the walkthrough. You can see how it used the prefetching via TRPC. So it noticed absolutely everything we did here. As always, some useful sequence diagrams here, which are the most interesting thing to me. because it's really important to understand what we have built. So if you want to, you can even pause your screen, and I'm going to zoom in for you so you can see. Just try and understand what's going on here, because these are really, really good diagrams here. And let's see if it left any comments. So these are potential issues, and you can see how it doesn't know Tailwind version 4 yet. This is, unfortunately, something that we're going to have to work with With all AI models, I don't think a single one knows the new syntax. So yes, this is not correct. We are using the correct version. Don't worry about that. In here, they suggest some refactoring, but I'm okay with this as it is at the moment. The to-do skeleton for checkout button. Yes, that's in to-do. Fallback for missing tenant name. we don't actually need this because the navbar is in the use suspense so we are certainly going to have the name so we can merge the pull request and we can confirm merge i'm not going to delete the branch instead i'm just going to confirm that we have lesson 16 here and now as always we are going to check out to our main or master branch and we are going to pull origin master and then git status on your main or master branch to confirm that you are up to date. That's it. That is the chapter. Amazing job and see you in the next one.",
  "transcript_chars": 490424,
  "transcript_filled_at": "2026-06-06T16:01:23.802782+00:00",
  "transcript_filled_by": "tk-bulk-groq-retry-20260606"
}