{
  "video_id": "N_uNKAus0II",
  "title": "Build a Finance SaaS Platform With Nextjs, React, Honojs with CSV Upload (2024)",
  "url": "https://www.youtube.com/watch?v=N_uNKAus0II",
  "transcript": "Hey there, my name is Antonio and today we're building a Finance Manager Software as a Service, a web app designed to give you control over your financial data. We'll start on the user dashboard, where you can view and manage your daily transactions, income and expenses through interactive graphs. Watch as I switch between different chart types for transactions and categories and see just how easily you can filter data by accounts and date ranges. Let's dive in and see all of the amazing features this app is going to have. In this segment, we'll explore the transactions tab. Here, a detailed table displays dates, categories, pays, amounts, and accounts. You'll see how to sort, search, and bulk delete transactions with ease. I'll demonstrate adding a new transaction via a drawer that slides out with a form ready to fill. Notice the intuitive select components for accounts and categories. Simply type and hit enter to create new ones. Plus, our smart amount component lets you toggle between income and expense types effortlessly. After creating a new transaction, I can quickly find it by searching our data table. Watch as I rename an account directly from the transaction, updating it instantly Next, I remove a category, setting the transaction to be uncategorized I will also show you just how easy it is to edit and delete any transaction from the data table Demonstrating the flexibility and real-time functionality of our system Now, let's try importing a transactions with a CSV upload I am immediately prompted to upgrade, a reminder that our project is also a software as a service. Next, I head to settings to connect our bank account using plate, but I encounter another upgrade prompt. Clicking on the upgrade redirects me to Lemon Squeezy, our payment provider. After completing the upgrade and returning back to the project, all previously blocked features are now fully accessible. It's exciting to see these powerful pools unlocked, allowing us to seamlessly import transactions and connect directly to our bank accounts. Now let's explore one of the key features we just unlocked, connecting to our bank account using a plate. After selecting my bank, I am redirected to login and asked which accounts I want to link. Once I complete the setup, a success message confirms that everything is connected. And now let's take a deeper look back in our finance manager. You'll see a comprehensive update across the board. New transactions have been added, accounts are up to date, and even the categories have been refreshed to reflect the latest financial data. It is incredibly seamless, providing a secure and efficient way to manage your finances. Now let's explore another feature we've unlocked. Importing transactions via CSV. After uploading the file, an editable data table pops up. Each header is a select component, letting us map columns to specific fields. The Continue button shows how many fields we've set, and if we don't need some data, we just select Skip. After setting up our fields, we then choose the account where we want to import the data. And once the import is complete, I'll showcase how the imported data is now fully integrated and displayed in the Transactions tab. This precise control ensures that the data fits perfectly into our financial management system. And to wrap up, let's check out the settings where you can disconnect your bank or cancel your subscription anytime for full control. This will ensure that you have full autonomy over your data and your services. And now, without further ado, let's get started. If you're interested in an extended version of this course, where we learn how to implement a monthly subscription and a paid plan using Lemon Squeezy, as well as how to connect to a bank account to fetch transactions using Blade, make sure to check out the link in the description and join CodeWithAntonio Pro to unlock the source code and all additional content for all of my projects. Let's get started by configuring Next.js inside of our project. In order to run Next.js, you're going to have to install Node.js. Or alternatively, you can install Bun, which is something I'm going to be using for this tutorial. Bun is a new JavaScript runtime with significant performance improvements over the existing Node.js. But if for whatever reason you want to use Node, you can do that. Let's go ahead and run the automatic installation command. As you can see, they are using the npx command. But since I just said that I'm going to be using Bun, I'm going to be using the alternative bunX. You can choose which one you want to use. Let's go ahead inside of our Visual Studio Code and let's open up the terminal. Inside of here, I'm going to run npx create next app at latest and then I'm going to give my app a name. In this case, I'm going to call it finance tutorial. Let's press enter and let's go ahead and set up the configuration. We're going to be using TypeScript. We want S-Lint. We are going to be using Tailwind, but we are not going to be using the source directory. We are going to be using the app router, and we are not going to change the default import alias, meaning we're going to leave it at the at sign. Now let's wait a second for all of this to install. Great. After you get the success message, go ahead and open up your project. I'm going to go ahead and press on finance tutorial, and you should be greeted with a message similar to this, at least inside of Visual Studio Code, and you can safely press yes. For the app structure, you should see something like this, an app folder with a layout and page inside, public folder, and some configuration files. Depending on whether you used bun, meaning bunx, or you used npx, you are or not going to see this bun lock file. So I am using bun, so I have this file. If you don't have this file, that's completely fine. It just means that you're using node or npx. Both are completely fine for this entire project. Great. Now before we run our app, I want to install our, well, not a component library, but let's call it a command line interface, which is going to help us to build our component library. So why am I explaining this in that way? Well, that's because I don't want to call this a component library because the moment you click in the documentation, you have a sentence which says this is not a component library. Instead, it's a collection of reusable components that we can copy and paste into our apps. So that's perfect for our use case because we want to build our own component library. Let's press on the installation. Let's select next.js right here. We've already run this command, so no need to run this. But now we have to run the following npx chat cnui latest init. Or if you're using bun, it's going to be bun x for you. So let's go ahead and copy this command. Let's head back inside of our terminal. And I'm going to run bun x chat cnui latest. My apologies for this. Like this. So banx chatcnui latest init, or you can use npx if you're using Node. I'm going to be using the default style, and I'm going to select the slate color. I'm going to be using CSS variables for colors. Let's wait a moment for this to install. Great. After you've installed this, you should see some new folders, such as the components, which should be completely empty, and the lib, which now holds the utils file. Inside of here, you're going to have a cn method, which is going to be very useful for when we want dynamic tailwind classes, but without conflicting the styles. Now we are ready to run this project. Let's go ahead inside of the terminal. If you're using Node, you're going to run npm run dev. But if you're using bun, you're going to run bun run dev. Both should start the project on localhost 3000. So let's go ahead and open the project. And there we go. You should be seeing the Next.js landing page. Now let's go ahead and let's add a button component from ShatCN library. I'm going to go ahead and find the button component. And in here, you should see the installation instructions right here. So whenever you click on here, you can select whether you want to copy it as an npm, yarn, pnpm, or bun instance. So since I'm using bun, I'm going to be copying the bun installation instructions. So let's head back inside of our terminal here. I'm going to go ahead and open a new one, and I'm going to run the command. You can also use a simpler version. So this is what was copied from here. But you can just do bunx chat cnui at latest add button. This is simpler. Or you can use npx chat cnui latest add button. There we go. So just ensure you have your project running here. Let me zoom in a bit. And in here in the components, you should now see the UI folder which holds the button component. So this is the key difference between ShadCN UI and all the other component libraries. So ShadCN UI inserts the code of the button inside of your project. So in here, I can change whatever I want. If I don't want these classes, I can modify them here. If I want to add another variant, I can do that easily. So let's go ahead and try this out. So just make sure you have your local host running. And let go inside of the app folder The app folder is where our client and server routing is going to live More specifically the page and layout files So the page is a reserved file name in Next which indicates a route. Next.js uses file and folder based routing. So if you have a page file purely inside of the app folder, which is not surrounded by any additional folders, this represents the root page. So that's why we can find this page right here in this code. I'm going to remove this import and then I'm going to entirely clean the return method and I'm going to write a new one. Inside of here, I'm very simply going to add our button component from components UI button and I'm going to say click me. Let's save and there we go. We now have a button which says click me. And now we can access all the props which we just looked at a moment ago. So we have class name, variant, size, ass child, and some other props which are extended from the button HTML attributes itself. And we can also try out these attributes here. So how about we give it a variant of destructive? That's gonna change the button to red. And here's a cool thing. If you want to, you can add your own variant. For example, blue, bgblue600, text light. Now if I go back, you can see how TypeScript is going to automatically recommend blue to me. And there we go. Now the button is blue. You can always remove this variant. And when you go back, you're going to get an error. This was an intro of our project and the technology that we are going to use. Next, we're going to learn how to build our landing login page, how to set up authentication, and we're going to get introduced to Honol. which is going to be our library for building the API. Now let's go ahead and let's prepare our authentication routes and our protected routes. So I'm going to be using a concept inside of Next.js calling route groups. So let's go ahead over the basics of routing. Let's say I want to create a new client route called test. I would first create a folder inside of the app folder, and inside I would create a page.dsx. What's missing for this to become a route is a default export. So something like this and I'm going to call this test page. I'm going to return a div calling text page like that. So what's crucial here is that you do a default export. If you just do an export const, this is not going to register as a route. So it's very important that whenever you have a page.tsx somewhere, you do an export default. So let's try that out. On localhost 3000, make sure you have your app running. Go to slash test and you should see the test page. So now you know that the folder name will take part of the URL. But what if you don't want your folder to be a part of the URL? You can do that by using a route group, something like this. Let's call this out inside of parentheses. And then inside of here, let's create a sign in route. And let's go ahead and create a page.tsx inside. Once again, I'm going to do a sign in page default export, div sign in page. And how do you think I'm about to access this route? I just told you that one way to not have your folder name inside of the URL is by putting it inside of parentheses. So what you can do is instead of going to localhost 3000 slash out slash sign in, this is a 404 page. Instead, out doesn't exist in the URL. So it is just slash sign in. So that was a quick explanation of route groups. If you created the test route, you can now remove it, but go ahead and create your own, well, route group for out pages. So we're going to create the sign in. We can copy this and let's create a sign up page. And let's go ahead and change this one to be a sign up page. And let's rename this to sign up. Now you should have a localhost 3000 slash sign in and slash sign up. There we go. Also, the default export name of the function does not matter. So this can be called x and it's not going to change anything at all. Right. All that matters is that you do a default export. Great. One more thing that we're going to do before we add our authentication library is we are going to create a catch all route. So usually if I wanted to create a route which can accept a parameter like a user ID, I would create a folder inside of parentheses user ID. And then I would access that inside of my component. But what if I don't know what I'm going to receive? I simply want to catch every single parameter, every single query, whatever is possible in this route. That's called a catch all route. You can read more about that in Next.js documentation. So what we're going to do here is create this double square parenthesis, and then I'm going to spread sign in. And very simply, I'm going to drag and drop this page inside. That's going to open this weird unsaved page.ts file. So this is, as you can see, it triggered a .next change. Don't worry about this. Here are the steps to resolve this if this happened to you. First of all, immediately close this .next folder. I see a lot of people accidentally continue to develop inside of here. This is not your repository. This is cache. So simply close this folder. If this page has opened for you, just click save. So you no longer have that circle here. And then you can just close it. If for any reason you think you've done something wrong, don't worry at all. Here are the next steps you can do. Go ahead inside of your terminal here and shut down your app. Then go ahead and remove the .next folder entirely. Then simply go ahead inside the terminal and run your app again. Remember, I'm using bun, so I'm doing bun run dev. If you're using node, it's going to be npm run dev. And the moment you run this, you're going to see the .next folder being reinitialized. So you don't have to worry if you mess something up inside. Make sure you refresh your localhost every time you shut down and restart your server. Now let's go back inside of the app folder out. Inside of here we started working on this catch all route. So what I've done is I've moved the page.tsx from not just being inside of the sign in folder but inside of the catch all route here. So what has changed? Absolutely nothing. This is still on localhost 3000 slash sign up. But now this is going to give our auth provider the ability to catch every parameter, every callback URL, and everything else it needs to properly handle redirects from this authentication page. Now I want to do the very same thing but for sign up. So go ahead and simply spread sign up here. And again, drag and drop this inside and repeat the steps. Go ahead and immediately close the dot next folder. Go ahead and enter this unsaved file and just save it and close it. Make sure you are not developing inside of the .next folder. And again, if you think you've messed it up, remove the next folder and restart your app. Great. Now you should have the sign-in and the sign-up pages ready to be used. And now I want to go ahead and I want to create another route group called dashboard. So the reason I'm using route groups is only so I can organize my files in a better way. So I could have created the sign in and sign up purely inside of the app folder, but I like my files organized. And this is very useful because then inside of this out folder, I can also create a components folder if that's something I want. So I can co-locate items here. Great. So I'm going to remove this empty folder. Make sure you didn't accidentally remove this components, we need that. Great. So inside of this dashboard, what I want to do is I want to make this my root page. So I want this, what's on the localhost 3000, this button. I can do this very easily. Since dashboard is inside of parentheses, it means that it will not be a part of the URL. So I can just drag and drop the page.vsx inside. And then again, close the .next folder, go ahead and save this cache file and close it and don't open .next again. If you accidentally messed it up, you can remove the folder and just restart your server. And nothing should change now. So inside of your dashboard page.esx, you should still have this button and when I refresh, everything is still working fine. I'm now going to remove this button and instead I'm going to render a paragraph which will say this is an authenticated route. So only logged in users should be able to see this, this authenticated route. So we have an issue. We are not logged in, but we are able to see this route. That's not something I want. When user tries to access the dashboard, the root page, I want them to be redirected to either sign in or sign up. So let's go ahead and install Clerk inside of our project. So head to clerk.com, create an account, and find a way to create a new application. If you already have a couple of projects, you're going to see a screen similar to this. If it is your first time, you're going to be redirected to a screen like this. So I'm going to call this Finance and go ahead and select any amount of providers you want. You can even select some Web3 options here. I'm going to keep it simple with Google and email login. In fact I just going to keep Google or basically whatever you want Go ahead and create your application and then we going to follow the steps to add this inside of Next So in here, let's go ahead and install clerk Next.js. I'm going to be using bun. So I'm going to be using bun add instead of npm install. You can use npm or yarn or pnpm. It really does not matter. So let's go ahead inside of the terminal here. I will shut down the app for now and I'm going to do bun add clerk next JS. Or you can do npm install clerk next JS. And while this is installing, let's see our next step. So our next step is to set your environment variables. So inside of .environment.local, we need next public clerk publishable key. and clerk secret key. I'm going to go ahead and copy both of those. I'm going to go inside of my project. Let's see if we already have a .environment file. Looks like we don't. So let's create one. In the root of our application, create .environment.local. Here's what's important. Whenever you're working with environment files, make sure that they are inside of gitignore. So inside of here, you can see that I have .environment.local. which means that this file will not be committed to GitHub. Do not commit to GitHub if you haven't put that inside of your Gitignore. You can also directly write .environment.local. You don't need to know these wildcards at all. So let me go ahead and do something. I'm going to press Command-Shift-P or Control-Shift-P on Windows, and I will reload the window. The reason I'm doing this is because I want to show you that this .environment.local has a bit of a darker or grayed out color as opposed to for example public folder or the file below that same as node modules so this is a visual cue for you to confirm that this file is not going to be committed and that will tell you that it's safe to put these two environment variables inside so let's just repeat where I got those right here in step two set your environment variables. I copied the two of those and I've simply pasted them inside of our newly created .environment.logan file. Let's see what our next steps are. So we are using the app router and we have app folder and layout.tsx. So it's asking us to add clerk provider to our app. I'm going to copy the top line here. Let's go inside of app folder layout. And in here, I'm going to go ahead and add that here. And then I'm going to wrap my entire project inside of that. There we go. But we are not done yet. One crucial thing we are missing is middleware. So what we can do is we can copy the entire middleware from here. and let's create a root file middleware.ts. Make sure you don't misspell this file as middleware is a reserved keyword in Next.js. Let's paste that in here and let's go ahead and try and run our app. Actually before we do that let's just see okay they tell us to run our app so I think it should be good enough for us to try this out. So inside of terminal, bun run dev or npm, let's visit our local host and let's see what's going on. There we go. So this is still telling us that this is an authenticated route. So we can still visit this, right? So let's learn how we can protect this route. So a big change has happened in clerk since my last tutorial. Usually the clerk middleware by default protected all routes. That is no longer the case. From now on, we decide which routes are protected, which means that all routes are public by default. So how about we explore the documentation here, the guide. So while we are here, we can already do this. We can build our sign up and our sign in page because we have this exact structure already set up. Let's take a look at it here. So I'm gonna collapse this and let me expand my file browser here. In the app folder, we already have sign in and sign up. Let's go inside of this catch all page.psx and let's go ahead and copy this. And I'm copying sign up. So it's a better idea to go inside of sign up, right? So we can literally select everything and paste it here. And make sure the path aligns. So slash sign up. Slash sign up. Let's save this. And now let's do the same thing for sign in. So step two, I'm copying it and going inside of here. Replacing everything. Slash sign in. So this is the correct route. Because remember, the out is a route group. And this is a catch all. So none of this will interfere with the fact that the only route we have is sign in and sign up. Great. And now let's go ahead and update our environment variables. So they need to match as well. Let's go inside of dot environment dot local and let's add. Next public clerk sign in is slash sign in and sign up is slash sign up. Perfect. And now what I want to do is I want to learn how to protect our routes. So as it says right here, we should now be able to manually visit our routes. So localhost 3000, there we go. We have our sign-in route here. And if I go to localhost 3000 slash sign up, there we go. We have the sign-up route here. So both of these seem to be working just fine. But we still have this issue. Why can we visit localhost 3000? So inside of this guide, I'm going to go ahead and I'm going to look at clerk middleware here. And inside of here, we should get a quick guide on how to protect our routes. So this is our current case, if I'm not mistaken. So if I go to my middleware, there we go. So just an empty clerk middleware with a matcher, which catches pretty much everything except the static assets. So let's go ahead and do this. Let's create the is protected route using the create route matcher. So I'm going to go ahead and paste that here. And we can import the create route matcher from clerk Next.js server. So let's just add this. There we go. But we don't have slash dashboard. Instead, all we have is a slash route. And make sure that you remove this wildcard asterisk here. because what this represents is dashboard and everything after dashboard. So in our case, it would be slash and everything after slash. But that's not true because there are some routes which we want to be, well, not public, but not specifically protected by the next JS middleware. And also remove this. So for now, just add this. But we're not done yet. So this just created a constant. What we have to do now is we actually have to create a logic, which is going to, well, work on protecting the routes. So this is what I recommend that you do. Go ahead and open this method. In here, in the parameters, you have access to out and request. Let's also prepare by adding an import of next response from next server. And if is protected route the current request. In that case, let's call the out.protect. Otherwise, let's return next response.next. So just like any other middleware, if you've ever used Express or Node.js before, it's going to return next, meaning it's going to go further. There we go. So now, if we've implemented this correctly, And if I try and go on localhost 3000, there we go. I am now redirected. And you can see how now I have this redirect URL parameters right here. So all of that will now work flawlessly because we made sure that we added these catch all routes. If you don't use these catch all routes, I think clerk is actually going to throw you some errors. So I didn't come up with the idea of this catch all routes. right? I simply knew it because I already went over clerk documentation. So that's where I found those guides. Great. What I want to do now is I want to make our pages just a bit prettier, right? So let's go inside of sign in page.tsx and let's go ahead and wrap this up like this. So we have more space to work with. Let's open up a div here and inside of here I want to give this a class name of minh screen so it takes up the full height and I want to work with grids so I'm going to initialize a grid calls one and now this doesn't make too much sense right why am I initializing a grid if I'm only going to have one column that's because I'm working on a mobile first design so on mobiles it's going to have one column But the moment we hit a large breakpoint, it's going to be grid calls 2. And now let's go ahead and open up another div, wrapping up our sign-in here. And inside of here, I want to give this a class name of full height. On large devices, we're going to work with flex. We're going to have a flex column, items center, and justify center. And px is going to be 4. Like this. And then inside of that div, let's create another one. With a class name of text center space white 4 and padding top of 16. Let's create a heading element here, which says welcome back. Let give this h1 element a class name of font bold text 3xl and let go ahead and give this a text muted foreground Or perhaps, how about a specific hex color 2E2A47. There we go. That looks better. And now below that, I'm going to open up a paragraph, so like this, And I'm going to say login or create account to get back to your dashboard. Now, let's give this a class name of text base. And let's give it a text, again, a specific hex color of 7E8CA0. There we go. And now let's go ahead and open up a div here, which is going to wrap our sign-in element inside. Let's give it a class name. Flex item center, justify center, and margin top of 8. There we go. So it's nicely moved and centered now. and what I want to do now is I want to add a little loader here, and let's expand just so you can see how it looks like. So this is how it's going to look on desktop, right? So our login is going to be on the left side, and in here we're going to have the logo of our application, but on mobile it's just going to collapse to the middle here, and on tablets as well. So what I want to do now is I want to make sure that when I refresh you can see how there's this blink, right, where this element doesn't exist. So we can take care of that by leveraging a couple of things from clerk next JS called clerk loaded and clerk loading. And let's also import loader2 from Lucid React. If you're wondering where we got this package from, is from chat cn ui so the moment we run chat cn ui latest initialize and then it offered us two styles default and new york if you chose default as i told you to do in the tutorial you're going to have lucid react inside of your package json so if you go ahead here there we go you're going to have lucid react if you chose a new york style you're going to have radix icons whatever you have you can manually add this one if you want. So make sure you just add some kind of loader icon. And then let's go ahead and do the following. I'm going to wrap this inside of clerk loaded. So this will only show when clerk has finally loaded. And below that, we're going to add a clerk loading. And inside of here, I'm going to render that loader to element. and let's give it a class name of animate spin and text muted foreground. So now when I refresh, there we go. We have a nice loader right here. Perfect. And now what I want to do is I want to go just by the end of this div right here. So let's go to our top div. As you can see in here, let me try and expand this so it's as clear as possible. So our top div initializes a grid, which means that this is the first column. So in order to take care of the second column, this is where we do that. So I'm going to go ahead and give this a class name of hype full, bgblue600. And then by mobile first, it's going to be hidden on mobile. But on the large, it's going to become a flex component. and then we're going to center all the items inside and also justify center all the items inside as well so let's take a look if I expand now there we go you can see how this is now blue but if I go to mobile it's just a single view what we have to do now is add the logo of our application so we add assets inside of the public folder here you can use any logo you want I'm going to be using the logo ipsum right here. So I already have this, but let me download the SVG again, drag and drop it inside of the public folder right here. I'm going to go ahead and rename this to logo.svg. And since this is an SVG file, we can easily find colors like this and change them to be white. There we go. So these two instances, you don't have to do this, right? or you can use any logo you want. Let's head back inside of our app out. So whatever file you did this to, it doesn't matter if you did it in sign up or sign in because we're going to copy and paste either way. So I started working here, right? I added BG Blue and let me just restart my window because it looks like my Tailwind extension stopped working. There we go. Yeah, about that Tailwind extension. So if you're wondering how I have this useful little snippet here and how come when I hover, I get this or how come when I type, I get out the completion here. That is all thanks to the Tailwind CSS extension library. There we go. So just make sure you have this installed. If you're working with Tailwind, it's extremely useful. Now let's go ahead and let's import image from next image. it's actually default like this there we go so in here let's add an image and let's give it a source of slash logo.svg or whatever you put inside of your public folder so I put logo.svg so that's what I have and let's give it a height of 100 and a width of 100 and an alt of logo and let's try it out there we go what I want to do now is I want to copy and paste this entire thing and I want to add it to the other sign up page so let's quickly do that I will select everything and paste and I'm simply going to change this to import sign up from clerk next js and add sign up here. Make sure you also fix the path. So this is inside of the sign up folder. So sign up. Let's try it out now. If I click on sign up here, there we go. So both of my routes are now working. Let's try this one more time. If I go to localhost 3000, I am immediately redirected here. Now let's try logging in. And there we go. we are redirected back here, which says this is an authenticated route. If you want to log out, here's a quick way you can do that. Go inside of your app, inside of dashboard, page.tsx, and go ahead and import the user button from clerknext.js. And in here, just render the user button. And let's add the after sign out URL to go back to the root page. make sure you add this and refresh your page i'm going to zoom in and now you're going to have an icon of the currently logged in user perfect so in here you can manage your account and you can see some stuff here or you can just sign out and there we go we are back to the root page perfect you just implemented signing in with clerk and in here you should actually be able to see your first users and then you can manage the users. You can ban them, impersonate them and do a bunch of other things. Perfect. So in here, you also have this labor secured by clerk. If for any reason you don't want to see that, you can go inside of customization, branding and you can turn it off. There we go. So now if I refresh, there we go. No more clerk instances. Perfect. You now have a fully working authentication. What we're going to do next is we're going to learn how to write API routes inside of Next.js, and we're going to incorporate HONO as an alternative way to write API routes. And I think you're going to like why we are going to be doing that. Great, great job. So now that we've wrapped up authentication in our project, it's time for us to create our first API route and to also integrate HONO inside of our Next.js project. So first of all, how do you build API routes in Next.js without HONO? So we learned that page.tsx is a reserved keyword for files when representing client routes. Whatever folder page.tsx is in is going to become part of the URL. Well, same is true for writing API routes. So here is the structure that I prefer. I always create an API folder and then inside I create an API route. For example, test. So this will be the equivalent of localhost 3000 slash API slash test. But instead of writing page.tsx, we write route.ts like this. And inside of here, using route handlers, we define which method is this for. For example, export constant get. This will represent the get method. So I can go ahead and write return next response, which I can import from next slash server. And my apologies dot JSON. And I can write hello world just like this. So now if I go ahead and write slash API slash test, there we go. We get our JSON object back. But if I wanted to have some parameters here, what I would have to do is I would have to create an additional folder, for example, test ID, and then I would need a route.ds inside, and then I would have to access this again. So let me copy this and paste it here. In order to access this test ID, we would have to destructure first the request, and then we would get\nthe params. So let me just align this like that. So it's easier to look at. First of all, the request would be a type of next request from next server. And in here, we would destructure the params. And that would be a type of params, test ID string. So why test ID here? Well, because that's what we gave this folder the name. So then I can keep the hello world, but I can extend this by also giving it a test ID to be params test ID. So if I go to slash API slash test and then slash one, two, three, I should get that in my JSON object. So API slash test one, two, three, and there we go hello world test one two three and if you wanted this to be a post request you will simply change it to post or patch or put so if I change this and refresh there we go I get 405 meaning that we don't have a get request on this route and if I bring it back it's here So this is fine. We now know how to write API routes using your regular Next.js. But what I want to do is I want to use HONO. So HONO is, as it says here, a web application framework. It is fast, lightweight, and built on web standards. And what's the coolest part about it is that it has support for any JavaScript runtime. So in here, they give examples for Cloudflare, Fastly, Dino, Bun, AWS, or Node.js. So let's go ahead and see what this is all about. So as you can see in here, this has more of a structure similar to Express or FastAPI, something like that, if you worked in your separate Node.js environments. And I honestly prefer this for building API routes over folder and file-based structure. So folder-based structure for me is fine for front-end routes. So this is okay to me. But API routes, I'd rather have something like this. So let's go ahead and see how we can add this. So in this quick start, they are giving you examples if you're building from scratch, right? But we are not building from scratch. we already have our project. So instead, what we're going to do is we're simply going to install Hono inside of our project. So I'm going to shut down the app, and I'm going to write bun add Hono, as simple as this. And now I'm going to try and find exactly where in the documentation they explain to us how we can add this to a Next.js project. So let's see in here in getting started, we have options for Vercel. And there we go. So it's right here. If you use app router, edit app API and then create a catch all route and inside a route.ts. So basically what we have to do is we have to create an endpoint that from now on is going to go through HONO instead of going through regular route handlers. So we're going to reserve the API folder for that. I don't know if I made that clear, but when making API routes, you don't need to use the API folder. So this is just my preference and obviously a preference of many since they recommend having this folder as well. So we are going to reserve this folder for Hono. So I'm going to go ahead and remove the test folder. We no longer need that. and instead I'm going to go ahead and create a catch-all route like this. So just spread the route inside. And then I'm going to create a route.ts file. So let's go ahead and do this exactly. I'm going to copy this snippet here. It's very short, so even if you don't copy it, you can pause the screen and write it yourself. So there we go. Let's take a look at what was just written here. So first of all, we add the imports for Hono. Then we add an import to handle our router from Honol slash Vercel. And what's cool about this is that in here, you can add any adapter you want. For example, AWS Lambda, or for example, Bun, if you're just running on Bun, or Cloudflare Pages, Workers, so a bunch of different things here. But since we are working with Next.js, let's keep it to Vercel. Then we can also define the runtime to be Edge. If you want to, you can turn it off or you can also write Node.js. So for now, I'm going to keep it on the edge. But later, we're actually going to have to turn off the edge because of the played integration that we will do. So played will be used to connect to our bank accounts. And played uses Axios on the backend, which unfortunately doesn't work well with the edge. So later, we're going to have to remove this. But for now, let's just keep it. So we work with this example. So in here, we initialize a new Hono app, and we immediately chain the base path to be slash API. So that works for us because we are in the API folder. So that is correct. And then we initialize a slash hello route, and we very simply return. So what is a C? They write a C everywhere. You might also see CTX somewhere like this. That basically means context. That's how you can read that. So let's go ahead and try this out. And so in here, it's pretty clear what we do. We define a get route to go to slash hello. And what we do is we return a JSON object with a message hello next JS. Below that, we enable the Hono to work on get routes and post routes. So I don't know if you kind of noticed this already, but this get and post are actually the route handlers that we just built a moment ago. But instead of doing export const get, and then inside of here, we return next response.json. Instead of that, we tell them, no, from now on, you're just going to handle the app. And app is HONO. So that's how HONO works. We kind of overwrite existing Next.js route handlers. So we don't have to do that file-based routing. So let's try it out. if I did everything correctly now, if I refresh this, I should get a 404. And I think I need to have my app running because I shut it down. So make sure you do this as well. So now it should be 404 here, if I'm not mistaken. There we go. And then let's go ahead and let's attempt to go to slash hello. My apologies, API hello. And there we go. We have a message saying hello next.js. Here's a quick tip for you. So let's try something else. I don't know if I explained this enough, but I'm logged in currently. If I log out and if I attempt to go to slash API hello, so this will still work. Great. I just wanted to confirm that in case you were having any issues. Perfect. So we just confirmed that our integration of Hono is working. So now if you want to, you can explore Hono yourself or you can also take a look at the workshop on my website. So in here we have a workshop where we actually build a very small Twitter clone with Hono. And we actually use next out, drizzle, and Hono all in one combination. So it's really, really cool. Great. So now let's actually go ahead and let's try and do something else. So for example, if I go to, let's go ahead and add an app.get here. Let's go ahead and chain another route, .get slash hello, slash test. for example, like this. In order to get the test, what we would have to do is get const test, and then we would write c.request.param, and you can see how we have an autocomplete for test. So that's another thing that I absolutely love about HONO is that it has end-to-end type safety. So let's go ahead and return c.json. Let's go ahead and give it a message of hello world. And let's pass in the test to be, well, our test from params. So if I go ahead and go to slash API hello slash 123, there we go. We just had the exact same thing. But you can see that this is even safer because if I try to write test 123 here, you can see that in my editor, I already have an error here. So I don't have to wait for this to be a bug for me. And then I go and debug. No, I already see that this program does not exist here. So that's why I prefer this way of building API routes. But that is not all. Hono is extremely powerful. And what we are going to do with it is we are going to build, let me just find it here. In the guides, they have RPC. So RPC is a feature that allows the sharing of the API specifications between the server and the client. And we're going to do that in combination with the ZOD validator. So as you can see here, for every route that you initialize with Honol, you can pass a bunch of middlewares inside. So where do the middlewares go? So everything between the route slash hello and the initialization of context, everything inside is a middleware. So there can be as many middlewares as you want here, right? So all of these things, for example, would be a middleware. So these are just mocks, right? But all of these things would be middlewares. So all of these are valid middlewares right here. And they can be validators. They can be authentication protectors. They can be permission protectors. So anything, right? So what we're going to do here, let's see if we can already try this out. So I want to use the Zod validator here. And I want to validate my perem here, for example. So I'm going to go ahead and see whether I need to install the Zod validator. We do. So let's go ahead and add Zod validator here. So go ahead and do this along with me. In your terminal I will shut down the app and do bun add Hono Zod validator right here And let do Bun run dev And we also need Zod So Bun run Zod Bun add Zod. All right. So now I'm going to go ahead. And I'm going to import Z from Zod. and then I'm going to add the ZOD validator here, like that. And then in here, we can add ZOD validation on the params. So let me show you how you can do this. So in here, you would add ZOD validator. Let's see if we can do this. Did I add the Zod validator? Sorry, Z validator. And inside of Z validator, you can define what you are validating. So if this is a post request, you can validate the form or the JSON. In our case, we want to validate the param. So inside of here, I'm going to write Z dot object. And I'm going to write the test needs to be a type of Z dot string like this. And then instead of using c.request.param, I would use c.request.valid. Let's see like this. And I would destructure test in here. And this would be so valid params. And there we go. Test is a type of string. And if I change this to number, for example, there we go. Test is a type of number now. And using the same method, you can also, for example, validate a form. So now we don't need this right. Let's say I chain a post method here. Let me just get the context and let's return a c.json here. So in here, we can now add zValidator, json, and z.object inside. And for example, we would need I don't know a name which is a type of z string and we would need for example I don't know a user id z string like this so usually you would access the body you know without any validation here but now that we have the zod validator what we can do here is we can extract the body from c.request.valid and we would specify valid json and then in here we would get the name and user ID. And we know exactly what type of, let's say user ID is a number, for example, right? So we would know exactly those types right here. And here's the cool thing. So right now, we are typing the Z object here, right? We are defining the schema here. But later, when we add our database, we're going to go ahead and we're going to share the schema from the Drizzle database, and we're going to reuse it for validation of our API routes and for our form values on the front end. So it's going to be a seamless experience using RPCs, using Honol, Drizzle, and Zod. It's going to be very, very smooth and end-to-end type safety. So if you want to, you can also chain validators. So let's say you had this to be, I don't know, create slash something post ID. If you want, you can chain the validator. So you can add another one. And this one would not be a JSON, but this one would do the params, right? Post ID. And then below that, you would have valid params. And it will only extract the post ID, not params, but param. If in case you were wondering, right? So as I explained in the beginning, everything between the route and the returning of the context is a middleware here. Perfect. So I hope that kind of summed up Hono. What I want to do now is I want to create a protected API route. So, so far, we know that we can use the middleware to protect the routes. So if we wanted to, technically, we could add API asterisk dot if this is the syntax. I'm sure. I think it is. So now I think I'm logged out, right? So if I go to localhost 3000, I will be prompted to log in. All right. So I did something wrong here. So I'm going to go and write slash API slash. Let me just find the correct syntax. I think it's the opposite order. So I add a dot and then an asterisk. Let's see. There we go. So now if you attempt to go to slash API slash hello, you get redirected back to the sign in route. So if that is okay for you, you can do that. You can protect all of your API routes, right? But I don't want to do that. I don't want my API routes to be redirected here, right? So I want to be able to access API hello. But at the same time, I also want them to throw a JSON error if user is not authenticated. So we can do that as well. Inside of the documentation of Honol here, inside of middleware here, you can find all kinds of authentication middleware that exists. And even this is not the whole list because then you have this third party middleware here. And inside of here, you see that they support Sentry, Firebase, Quick, TRPC, whatever all of this stuff is. And we also have clerk out. And if you're interested, also out the JS or previously called next out. So I'm going to go ahead and use clerk out here. So I need to install this. I need to install Hono clerk out. And I also need the clerk backend. So let's go ahead and do those one by one here. so bun add pono clerk out and I also need the clerk backend like that let's confirm that we have all the necessary environment keys here so clerk secret key and clerk publishable key let's see if we have that so inside of my dot environment we have the clerk publishable key and we also have the clerk secret key but I think that we are gonna have to add the clerk publishable key one more time separately. So I don't know how much you know about next public, sorry, about environment variables in next, but if you wanna expose them to the client, you add the next prefix to it. But this one requires a clerk public publishable key, but in a form of server only. So here's what I want you to do. I want you to copy and paste your existing next public clerk publishable key and remove the next public in front of it. So it needs to match exactly what this documentation says, clerk publishable key, just like that. And later we're going to try to remove it just to see if it would have worked without it. And let's see how we use it. So first of all, we have to define, well, in here, they set all the routes to use clerk middleware. I don't want to work like that. I want to be specific around which routes are protected. So let's go ahead and import this too. So clerk middleware and get out from Honol slash clerk out. I'm going to go inside of my routes here. Inside of app API route. Let me just zoom in here. And I'm going to go ahead and add those two. So from Honol clerk out, which we've just installed alongside clerk backend, we now have the clerk middleware and the get out. So I'm going to go ahead and do the following. How about I clear up this entire thing where we learned some parameters and stuff about Hono? And I'm going to go ahead and I'm going to add a middleware here. So remember, everything in here, in this space, between this last method is a middleware. So let's go ahead and collapse this. And let's write clerk middleware right here. There we go. So now what we can do inside is we can go ahead and we can extract the user like this. get out and pass in the context. And then in here, we can check if there is no out user ID. We can go ahead and return c.json error unauthorized. I think I misspelled it, but it doesn't matter for this case. I just wanted to demonstrate this. So make sure you are not logged in. So just make sure that on localhost 3000 and also make sure you have the app running just a second bun around dev there we go let's just wait for this to refresh all right so it's redirecting me here if i go to slash api hello there we go i get an error back that i am unauthorized perfect so now this is working i'm going to go ahead and try and log in now and i'm also going to extend this method so alongside message let's also write out dot user ID here if we have one like this. So I'm going to pause the screen and I'm going to go ahead and sign in. All right, so I'm signed in. Let's go ahead and go to slash API slash hello. And there we go. We have a matching user ID from our clerk user. Perfect. So now our clerk middleware is working and we are separately protecting our API routes as opposed to doing it in the middleware. I think this is a better way of doing it because the middleware will redirect you to the root page. Perfect. So now we have that. And I want to resolve one more thing before we wrap up this chapter that I think you probably, you know, are thinking of. And that is, all right, but if we have a bunch of these routes, isn't this file going to become absolutely huge? Yes, but luckily for us, there is a solution for that. no, we're not going to write it all in one file here. So let's go ahead and see what they say about best practices here. So I think that you probably have something in mind of building controllers as they wrote here, right? So for example, in here, you wouldn't write a route, but instead you would have, you know, hello controller, right? Something like that. Well, as you can see in here, they say, don't make controllers when possible. So they call that Ruby on Rails controllers So the issue is related to types For example let see The path parameter cannot be inferred in the controller without Ruby on Rails like controllers So the issue is related to types For example let see The path parameter cannot be inferred in the controller without writing complex generics So that is the problem, right? Remember when I showed you how we can have type safety then infer params? You can't do that if you build controllers, right? They do have a solution for that. If you really, really want to create a controller, you can use something called create factory here. But we're not going to do that here. Instead, what you can do is you can build separate files like this one. So let's go ahead and just copy this example. So I'm going to go ahead and copy this right here. And I'm going to go inside of my route and I'm going to create outdoors.ds. And I will just paste this here. Right? So let me just return that. There we go. And then I'm going to copy this again. the one below it, and I'm going to call this books.ts. So separate files, right? There we go. So we have a get, a post, and a get of a specific ID. And now back in my root file right here, what we can do is we can import those two. So let's do that. There we go, like this. And we don't need clerk middleware anymore. So just import your authors and your books. And instead of using app.get, you would do app.route slash authors. And you would just use authors. And then below that, you would do slash books slash books. There we go. So now this is much more manageable. Each entity has its own file. And we still have access to the context and all the type safety inside. So if you want to go to slash API authors, we should get a return of text list authors. If I go to the book, my apologies, books, list books. If I go to slash book slash test, there we go. It says get test or authors. There we go. API authors test, get test. Perfect. So this is working as well. But this actually will require some changes later on when we build RPC. Right, so for RPC in here, you can already check this out. They have best practices, not best practices, but is it in the guides? RPC here. So in here, we're going to have to have one app type, which will hold all of our routes. So regardless if they are in different files or not, we're going to have to somehow find a way to hold all of those types together. And in here, they actually explain to you how you can do that. So let me go ahead and scroll down right here. They should have something called, there we go, using RPC with larger applications. So as you can see, the difference is that we're not gonna have this separate, let's go inside of outer. So inside of here, they keep calling the app again. So app.get, app.post, and app.get again. When we switch to RPC, we would have to chain those together. So we wouldn't have app.get, app.post. Instead, it would be app.get.post.get, right? So we would chain the elements. And same is true for this example of authors and books. You would chain them all under one app. And then your app type will be able to hold all of the types and context tests and params and all the ZOD validations together. But that's for later. I just want to give you a quick introduction to Hono. So go ahead and play with Hono a bit yourself. What we're going to do in the next part of the tutorial is we're actually going to go ahead and start going back into our UI so that we can build our navbar. Great, great job. So now that we've learned how HONO works, you can go ahead and leave the API route behind. Right. So we're going to go back on the front end now. So if you made these authors and books, you can leave them. If you haven't made this, you didn't have to. So what I'm actually going to do is I'm just going to return this back to hello. And I'm going to go ahead and simply get the context and return c.json hello world. So just the most basic API route here. And I'm going to remove the authors and the books. And I will also remove the respective imports for that. There we go. So just the simplest implementation possible. And you can leave that behind for now. We're going to come back to this later. For now, let's go inside of the app. Let's go inside of the dashboard. So one more thing that the route groups are good for is that they can share layouts. So what we can do now is for every future route that is going to exist inside of the dashboard route group, it's going to share a unique layout. So let's go ahead and create not a unique layout. I wanted to say it's going to share a common layout. So layout.tsx. As you can see, we immediately get an error here. So the rules of layouts are similar to the rules of page. It needs to have a default export. So I'm going to call this dashboard layout. The naming does not matter because this is a default export. So just make sure you export something. And let's for now return a div and say layout. So what's getting on now? why am I only getting the text layout rendered here? Let me zoom in so you can see. So it only says layout, but what I expect is that my user button is rendered, right? Or we can call this dashboard page. So how do I get that rendered here? Well, another rule of layout is that it needs to render the children. So let's quickly create the props children to be a type of react dot react node. And then in here, we are able to destructure those props children. And instead of this, we just render the children. And there we go. So now through this layout, we render the dashboard page. And every additional route inside is going to share this layout. So whatever we create on top of here is going to be reused. So how about we go ahead and change this from being wrapped in a big fragment. Instead of using div here, let's use main and let's give it a class name of px3. And on large devices, it's going to be px14. And then what I want to do here is I want to create a header component. So let's go ahead inside of our components and let's create header.tsx. So inside of here, I'm going to export const header. And I'm going to return a div saying header component. Now I can go back inside of app layout and I can import header from components header. And there we go. So now all of my routes inside of dashboard are going to have the header component above them. Perfect. So we are now ready to start styling this. Instead of a div, we're going to use the header element. And let's give it a slight gradient. So background, gradient to bottom. So you can always hover over a class name to see if it actually exists in Tailwind. but that will only work if you have the Tailwind CSS IntelliSense extension that I told you to install, I believe. So let's define from what color. So from blue, 700, to blue, 500. There we go. So a slight gradient, as you can see. Perfect. Let's give it a PX of 4 and a PY of 8. and I also want to define different values for large devices. So 14 and padding bottom of 36. There we go. And now let's go ahead and let's just style this further. So I'm actually going to zoom out because I want to be looking at this as if it were a desktop view. So I recommend you do the same. So either expand your screen or zoom out until, well, just keep it a bit larger. Because when we go ahead and create our navigation, we're going to have separate navigation for mobile and separate for desktop. So let's go ahead and create a div here with a class name, max with screen to Excel and mx of auto. So what does this do? So if I create a text header here, you can see that at one point, my header stops expanding, right? So that's what this does, max width screen. So we are only going to handle the responsivity up to a certain width of the monitor, right? So I don't want to support responsivity if the monitor is wider than this. We're simply going to push everything in the middle. if it breaks from this, right? You can see how header is expanding, expanding, expanding, and now it stops expanding. So that's what this does. Great. Now inside of here, let's open up a new div with a class name of pull, width, flex, items, center, justify between, and a margin bottom of 14. And then in here, another div, which is going to hold two elements together. So for that, I'm going to need flex again. And I'm just going to give them a bigger gap between if we are on desktop. And now let's create a header logo component. So inside of components, let's create a header logo.tsx. And inside of here, I'm going to import link from next link. and I'm going to import image from next image and I'm going to export const header logo. This is going to be a link with an href to the root page and a div which for now can just say logo. Let's go back inside of header and we can now import the header logo from dot slash header logo or components logo however you prefer it. And now you should just have a link which says logo at the top Let go ahead and give it an image which we already used inside of our sign and sign components logo So inside of here, let's give this div a class name. Items center is going to be hidden by default, right? So that's why I told you to try and expand your browser because I want this to be visible on large devices. So hidden on mobile. But when we hit the large breakpoint, we give it flex like that. And then inside of here, let's render our image component with a source of slash logo.svg and out of logo. And let's give it a height of 28 and width of 28. Below the image, let's give it a paragraph finance. So that can be the name of your app, right? You can write here whatever you want. So I'm going to give this a font semi bold, text white, text to Excel, and ML of 2.5. So if I expand this, there we go. We now have a nice logo. And whenever we click on this, it's going to redirect us to the root page. So right now it has no effect because we are on the root page. Perfect. So we have our finance and it disappears on mobile. So we want that because as I told you, we're going to have a bit of a separate design on mobile. Perfect. So that's it for our header logo. So we can close everything here and let's get back inside of our header. So I said that two elements are going to be inside. The second element is the navigation element. So let's go ahead and create that. Inside of components, let's create navigation dot TSX right here. And I'm immediately going to mark this as use client because I'm going to be using some hooks here, not immediately, but definitely in a moment. And let's go ahead and just fix the import error here. So navigation, and let's return native navigation. And I don't need a semicolon here, and I can now import navigation from dot slash navigation or components navigation. Great. Now, inside of the navigation here, let's go ahead and let's define all the routes that we are going to have. So I'm going to write export const, actually no need to export it, so we can just use routes. It's going to be an array of objects. First object is going to have an href of root page and a label of overview or dashboard, however you like it. Let's copy and paste this, and for the second one, we're going to have slash transactions, and that's going to lead us to transactions. Let's copy and paste this. Remember to add the columns. Sorry, the commas. Then we're going to have accounts. Whoops. Then we're going to have categories. and lastly we're going to have the settings page there we go perfect so now we have that and now i want to go ahead and i want to create a component called a nav button so actually before we do that let's try and just iterate over this routes array so that we can work with that. So it's not going to be a div. Whoops. It's not going to be a div. It's going to be a nav element with a class name of hidden on mobile, but flex or visible on large. And items are going to be centered. Gap between each of the element is going to be two. And overflow, x auto, in case we need to scroll. And now we're going to go over the routes. So routes.map, we get the individual route. And for now, we can just write route.label. You can ignore the error for now. So expand your screen and you should be able to see on desktop mode. So expand that much that you can see the logo. And then you should finally be able to see overview, transactions, accounts, categories, and the settings. So now what we're going to do is instead of rendering them as a paragraph, we're going to reuse this href and label as props for a component called nav button. So remove the paragraph and create a nav button here. So it doesn't yet exist. First of all, give it a key of route.href because that's unique for each of our routes. And then give it an actual href to be route.href. And label is going to be route.label. And now let's go ahead and let's add our hook. So that's why I marked this as useClient called usePathName. So inside of here, I'm going to get the path name from usePathName like that. And I'm going to go ahead and call this isActive if pathName is identical to route.hrep. So if you're confused about the useClient connection with the hooks, I'm going to go back in a moment and explain this. But let's just resolve this error here. So inside of components folder, I'm going to create a nav-button.tsx file right here. And instead of here, let's just quickly export const nav button. So we don't have to have that error. So div nav button. And right now, we're going to have prop errors. So how about we also wrap up defining the props? So type props in here. Let's give it an href of string. Let's give it a label of string. is active of boolean. And let's go ahead and assign those props here. So props, there we go. So that should work fine. Let's go back inside of the navigation and let's import this to .slash nav button or components nav button. I like to separate this from the global imports. There we go. So let's go ahead and try something. If I remove use client, There we go. I get an error here. What does it say? It says you're importing a component that needs use patname. It only works in a client component, but none of its parents are marked with use client. So they are server components by default. Right. So if you want to learn more about that, you can follow the documentation on this error here where you will learn the fundamentals. Right. So server components allow us to write UI and it can be rendered and optionally cached on the server. In Next.js, the rendering work is further split by route segments to enable streaming and blah, blah, blah. So you can read all about that. So what's important for you to understand if you're coming from a background which is not Next.js, meaning that if you're used to, you know, React and Byte, React and Webpack. So that is a form of building single page applications. What we're doing here is we are building applications with server components, right? So server components can be used as we just read to write UI. And well, one cool thing about them is that you can directly fetch from the database inside of them. So you can imagine them as your get API route if you want to. One thing that they cannot do is work with interactivity. And that also includes hooks. So whenever you need a hook in a component, you will mark that as use client. All right, when I save this, the error goes away. But let me just comment it out for a second just to discuss this. So why did they then say none of its parents are marked with use client? What does that mean? So if you want to, use client shouldn't exactly be explained as, all right, make this component interactive. Use client is more of a boundary, right? You open the client-server boundary at this point. So if you want to, you don't have to do it at the navigation level. You can do it, for example, in the parent, in the header level, right? So header is the one that renders the navigation. So if I were to mark the entire header as use client, that means that the boundary of client-server has been opened. And that allows me to use the hook inside of navigation component without marking it specifically as use client. So I just wanted to quickly explain that. So now you might be thinking, all right, so if I ever mark something as use client, that means that nothing inside can ever be a server component. Well, it seems like that, but there is a trick to do that as well. You can insert a client component inside, a server component inside of a client component. And we actually are going to do that in this tutorial. And you do that by using children like this. So if you pass server components through children, then you can safely mark something as use client. All right. We're going to come to that example at some point and I'm going to explain it again. Now let's stop the confusion and let's do the following. In the header, we don't need use client, right? So if possible, try and make the most of your routes server-side rendered. My apologies, not server-side rendered, but server components. So yes, server components and server-side rendering is not the same thing. If you mark something as useClient, that is still server-side rendered. It's just not a server component, right? So that's a big misconception that people have. Marking something as useClient still means that it's going to be rendered on the server. It's just not a server component. Nevertheless, we need use client inside of our navigation component because we want to access the use path name hook. And we're going to use it to check whether a route is active or not. Now let's go inside of the nav button here and let's go ahead and give it some cool, cool styles. So first of all, let's destructure the href, the label and the is active prop. And let's go ahead and let me see if I have inside of my UI, we already have the button component. Perfect. so let's render that button component so from dot slash ui button or components ui button and inside of here we're going to add a link component from next link we're going to give it an href of href and inside of link we are going to render the label the link was imported from next slash link like this great now let's go ahead and give this first of all as a child prop and then let's give it a size of small. Let's give it a variant of outline. And now let's give it a class name, but not just any class name. We are going to revisit in our list.\nutils, this CN method, which I told you was going to be used whenever we need to dynamically change tailwind classes. So technically, we could be doing this. We could open, we could do this, like is active, then something, otherwise something. We could do that, right? But you're going to notice that very soon when working with, you know, very, with different variants and dynamic classes, there's a lot of things that can go wrong. especially that one thing people don't consider is that Tailwind has a just-in-time compiler, which means that you need to write classes in full, right? So a lot of people try to do some weird tricks with this ternaries and stuff. What works best is truly having this combination, CLSX and Tailwind merge. So we're going to use that. So here's how that function works. import cn from lib utils as I just did and open up this function. In the first argument it's going to take all the default classes and then every additional argument can be a ternary for example is active can be something otherwise this right so that's how we're going to do that. So let's focus in the first argument here and let's define how we want our button to look. I want to give it a full width. On large, I want to give it an auto width. I want to give it a justify between. I want to give it a font normal. On hover, I want to give it a background white slash 20, meaning it's going to have an opacity. On hover, I also want to give it a text white. I want to remove any kind of border on the button. On focus, visible. I want to remove the ring by using ring offset and setting it to zero. Again on focus visible, I want to remove the ring by setting its color to transparent. I want to also remove the outline of the button. I want to make the text white by default. On focus, I want to change the background to be white slash 30. And I want to make all of that a bit animated using the transition class. And now if the element is active, I'm going to give it a background white with opacity of 10. Otherwise, background is going to be transparent. And besides this, let's also give it a text white. There we go. Let's try this out now. Perfect. So now you can see how since this is an active route because we are on the root page it has a different style as opposed to all the other ones and you can see how hover has its own style if you try and click on any of those you're going to get a 404 because we didn't build any of those now what's missing now is the mobile view right so you can clearly see that here so for that we are going to need to have a separate component called a sheet so let's go ahead inside of our terminal here and let's do bun bunx shad cn ui latest ad sheet like this and let's go ahead and import everything we need from that and let me just not forget to run my app so bun run dev there we go so inside of here let's import everything we need from components UI. There we go. We are going to need the sheet itself, the content, and the trigger like that. And then I actually want to install one more package, which we're going to, well, use a couple of times throughout the project. So bun add is going to be called the react use quick reminder i'm using bun if you're using node you should write npm install or yarn add or pnpm whatever it is right all right so make sure you add a react use package and then in here let's from react use import use media like this. And I'm doing this in a wrong component. My apologies. So I'm doing this in nav button. Where I meant to do this was in the navigation component. So go back inside of the navigation component and add the sheet components here. So I'm going to remove them from the nav button. And same is true for this React use import. So not in the nav button, in the navigation. Whoops, there we go. perfect so now that we have that we're going to go ahead and do the following let's go ahead and define whether our drawer is going to be opened or not so it's open and set is open use state from react by default false so just make sure you add the use state import here and then what I want to do is I want to go ahead and add the router from use router so that's going to come from next navigation make sure you don't accidentally import it the use router from next slash router so next navigation all right and let's define is mobile to be use media if open up parentheses, max width is 1024 pixels. And important for hydration, by default, let's assume that the view is not mobile. And then let's add on click here to accept an href, which is a string router dot push href and set is open is going to be false. You're going to see why we need this in a moment and that's because if we are on mobile we're going to have a completely separate return happening here so we're going to use our sheet component here and we're going to control it using the is open boolean and on open change is going to call the set is open so just like this now inside of here we need a trigger component and for that we're going to use a button so make sure you import the button. I'm going to change the, to go to components like this. So components UI button, there we go. And let's go ahead into the following. Let's give it a variant of outline. Let's give it a size of small. Let's give it a class name of font normal, background white slash 10 on hover, background white slash 20. Hover is going to have text white. border is not going to exist. And we can just copy from our nav button the focus visible ones. So those two. Focus visible ring offset zero and ring transparent. Those two. Outline is going to be none. Text is going to be white. Focus, VG white slash 30 and transition. Like that. And I think we also need border none. There we go. We already have it. Great. So font normal, bg white 10, on hover, bg white 20. On hover, we're going to have text white, border none, the reused focus visible with ring offset zero and ring transparent, outline none, text white, and on focus, background white slash 30 and transition. Great. and inside we're going to use a menu icon from lucid react so make sure you've added this there we go and let's go ahead and give this a class name of 84 and with four one cool thing in the new tailwind update is that this class name can now be replaced with size four so that's the equivalent right if you want to great so now we have a trigger but now we need a content here like this. So inside of here, let's go ahead and give it a side of left and let's give it a class name of px2. And let's open up our small navigation here with a class name of flex, flex column, gap y of two and padding top of six. So it gives a little space to the top because of the close button. And now inside of here, let's go over our routes.map. Let's get the individual route. And we are simply going to render a button component here like that. So I'm going to give this a variant. If route.href is equal to path name, in that case, it's going to be secondary. otherwise it's going to be ghost and let's go ahead and give it this a e route dot href and let's give it an onclick to be an arrow function which calls the onclick method and route dot href like that so why are we using onclick here instead of adding a link because link by itself will not close the drawer, right? So I want to handle the mobile view by going through our onclick method, which will use the router hook to redirect and manually close the drawer. There we go. And inside of here, we just render route.label. And I believe that should be it. So if I refresh my page, make sure you have your local host running here. So let's see if we did something wrong. So this seems to work, but nothing is appearing here. Let's see why is that happening right here. So I have max with 1024 pixels. I'm going to go ahead and console log is mobile here and try and debug. Oh, looks like I just had to do a hard refresh. Yes, looks like it was just cache. so nothing I did fixed it okay so if you want to you can uh if you have the same reason let's go ahead and just do bun run dev together looks like it was some weird cache inside so now again desktop works fine but on mobile now I have a drawer like this Perfect One thing that I might want to change is I want to move the text to be here in the beginning. So how about I go inside of my isMobile and how about I add a class name here? pull width and justify start there we go so that now looks better in my opinion so when i click overview there we go you can see how it closes the drawer and it's going to do that even in this 404 routes perfect so that handles our navigation component uh right here uh what we have to build now is the welcome message right here. And below that, we're going to have some filters. So before we wrap up the chapter, I want to do that. I want to add the, well, a little logout button here and a welcome message here. And we're going to leave the filters for later. So let's go ahead and go inside of our components navigation right here. not navigation header so we have header logo and we have navigation and now what I want to do is outside of this div I want to add a user button component and I want to give it an after sign out URL to go slash to the root page so from clerk next js import the user button here's one thing that I don't like when I refresh it doesn't exist for a second so we can solve that by using the clerk loading and clerk loaded components. So clerk loading and clerk loaded. If it is loaded, we're going to display this. But if it is not, if it's still loading, in that case, let's add loader2 from Lucid React. And let's give it a class name, size8, animateSpin. and text slate 400. So let's try it out now. When I refresh, there we go. We have a nice little spinner. And from here, we can sign out. Perfect. Now let's go ahead and let's create a welcome message. So clerk load that this is fine. So outside of this div right here, let's add a welcome MSG like this. So I'm going to go ahead and create a new component. welcome msg.tsx. And let's use client here because we are going to need to use a hook called use user from clerk next.js. Let's export const welcome message. And let's destructure the user and is loaded from use user right here. Let's return a div element. and inside of here we're going to have an h2 element which will say welcome back below that a paragraph this is your financial overview report or whatever message you prefer so go back inside of the header and import the dot slash welcome message, I'm going to use the components and there we go, we now have the welcome message here so I'm going to go ahead and give this div a class name of space white 2 and margin bottom of 4 I'm going to give the h2 element a class name of text to excel, on large it's going to be text for excel text is going to be white font is going to be medium flex actually no need for that And for the paragraph, we're going to give it a class name of text small, lg text base, and text is going to go ahead and be a specific color, 89B6FD, like this. There we go. So this looks nice. What I want to do now is I want the welcome back message to write out the user's name. So how about we do this? Let's go ahead and at the end of this, we'll come back, open an object and write if is loaded. Well, I told you to open an object. What I meant to say was open curly brackets, right? So if it's loaded, add a comma and a space. Otherwise, just space. So it's important that you add space here. and then chain along user question mark first name and then if you want to you can add additional space and you can add a little emoji like I'm gonna do like this like a wave and there we go so this will read the name I have this weird name from my gmail because I didn't want to enter my name so this will load the name from your google account If you used email login, perhaps your first name won't exist. So if you want to, you know, you can just say welcome back with a smile like this. You can just do this as well, however you want. Great. So that wraps up our header component, our navigation. Perfect. So what's going to be below this are two filters, the account filter and the date filter, right? But I kind of want to work on them later, right? What I want to build next is I want to start actually working on setting up our database and setting up our schema so that we can create our first transaction, right? That's what I want to do next. Great, great job. So now let's go ahead and create our database connection with drizzle.org and neon.postgres. So visit neon.tech or use the link in the description to obtain a free database. I'm going to go ahead and log in and create my first project. And I'm going to use the same name for the project and for my database here. And I'm going to press create project. After my database has been created, I need to find my connection string. And you can find that down here. So inside of here, you need this connection string. So we are going to be using NeonDriver, but it might be easier for you to copy it from here. So just select the simplest Postgres option and just copy the connection string. Or you can use the copy button right here. Go inside of your .environment variables and separate this from the clerk variables. So it's easier for you to see. And name this database underscore URL. And simply paste the Postgres connection string. So it needs to start with PostgreSQL and it needs to use this kind of format. Great. Save this file and make sure that you have no typos in your database URL. So now we're going to follow Drizzle or RAM instructions to connect to Neon. So I'm visiting the documentation here for Drizzle and I pressed on PostgreSQL right here. And the first option is actually Neon. but you can also take a look at all the other options like they have, like Superbase, Node, Postgres, and all the other options. So I'm going to be using Bun, so I will select Bun. If you're using NPM, feel free to leave it at NPM. So let's go ahead and first add Drizzle ORM and Neon Database slash Serverless. So I'm going to go here inside of my terminal, and I believe... So this is me in post-production here, just giving you a quick tip. So during the recording of this course, a new version of DrizzleKit came out. It is a great update, but unfortunately, it doesn't work with the scripts that I have prepared for this tutorial. So because of that, I want to teach you exactly which version of DrizzleKit, DrizzleORM, and the Neon database to install and how to do that. So looking at my package JSON right here, let's go ahead and search for drizzle. So this is the most important thing for this project. Your drizzle kit needs to be this version. If your drizzle kit is a version 21, that is the newer version. And the newer version is great. And if you want to, you can learn it on your own. It has some great adjustments. The problem is the migration scripts, the running of the database studio, and all the other things are not going to work if you are using 21. So when installing DrizzleKit, go ahead and install it like the following. If you are using npm, for you, it would be npm install-drizzle-kit, and then you would add an add sign and you would specify the version. So 0.20.17 like this. This is how you would install a specific version right here. If you were using bun, you would do bun add dash d drizzle dash kit and then add a specific version 0.20.17 like this. And lastly, I also want to show you my drizzle ORM package and my neon database serverless package. So I don't think any big changes are in those two packages, but still in case you want to do the exact same version. There we go. So for my Drizzle ORM, I'm using 0.30.10. And for Drizzle Zod, I'm using 0.5.1. And for my Neon Database Serverless, I'm using 0.9.1. So I recommend that you when you install this in this chapter you do the exact version installation so npm install drizzle dash orm and then go ahead and type out the exact version that i have so 0.30.10 or bun add drizzle orm 0.30.10, right? Or yarn, whatever you're using, just use these versions. But again, as I said, the most important part is this the drizzle kit needs to be this version Otherwise none of the scripts are going to work And the config file which we are later going to create so you don have this yet but this config file also changes It introduces something called the dialect, which as you can see in here does not exist. But if I look at the Drizzle documentation here in the Drizzle kit and look at the configuration, they have a dialect, right? So a lot of things has changed. We can do that in the next course or in additional chapters, but I already recorded a good portion of the tutorial. So please use this version of DrizzleKit in your dev dependencies. I also have my app running, so I will shut that down for now. And instead I'm just gonna add bun add Drizzle ORM and Neon Database Serverless or npm install two of those packages. And then we also need to add inside of our development dependencies DrizzleKit. So for me, that's bun add dash capital D DrizzleKit. For you, it might be npm install dash capital D drizzle kit. There we go. So now let's go ahead and let's follow the further instructions. So now what we have to do is we have to create our, well, let's call that root or entry point for our drizzle connection. So I'm going to go ahead and create a new folder. And I'm going to call this, let's see, let's call it database. And inside of database, I'm going to create a drizzle.ds endpoint. it. And now inside of here, we can copy and paste this snippet right here. So I'm going to close this and paste it here. There we go. I'm going to remove this because this is just the example, but this is what we need. So first of all, I don't like single quotes, so I will replace them to use double quotes. And the other thing that we have to do is we have to change this environment variable. So for us, we don't have the drizzle prefix. So what you can do is go inside of your dot environment local and directly copy the name of this variable. That's the safest thing to do. And you can leave the exclamation point because otherwise you get an error. There we go. Now let's go ahead and make sure that both of these constants are exported like this. There we go. What I want to do next is I want to start creating our schema. So let's go ahead and see how we can create schemas here. So let's go ahead and select the schema or maybe the overview would be better. There we go. So you have a lot of options about how you can create your schemas. So you can just use everything in one file. You can have separate files inside of a schema folder, or you can even have separate folders if that's what you prefer. I'm going to keep it simple and just use the one file because it's going to be easier for me to export everything from there using the asterisk option for my imports. And then it's going to smoothly go and align with my migration script. So I recommend that you do the same. So let's go ahead and do that. So this is how you basically do that. We don't have to copy it from here. Instead, what I want us to do is I want us to go inside of the database folder and create a new file schema.ds right here. And let's go ahead and create the very first account, which we can call, for example, I told you to create the very first account. I meant to say the very first schema, which is going to be the account schema, right? So this is how you do it. you write export const accounts pg table and you can import pg table from drizzle dash orm slash pg core pg represents postgres so obviously if you were using mysql you would have different imports and you can always check that out in the documentation here so as you can see here we are using the pg core imports but in mysql you can see that it has mysql core great so make sure you work with Postgres if that's the database you chose. So let's open up pg table and now what we do here is we also give this table a name. So I'm going to call this accounts and then we open an object as the second argument and inside of here we now write our elements. So I'm going to create an id here to be a type of text and you can also import test text from drizzle or mpg core. So make sure you have text imported from here. And now we give this field a name, ID. And let's also chain primary key. That's it. Now we have an account with ID. Perfect. So let's go ahead and add the following fields. I'm also going to add name to be text name. And let's make it required by adding not null. There's no need to add not null to the ID because primary key takes care of that. And let's also add user ID, which is going to be text user underscore ID dot not null. So what's important to understand with Drizzle here is that this is just a JavaScript, right? But what's inside of strings is what's actually being written to your database here, right? So just because we have camel case here doesn't mean that that's how it's going to be stored as a table. Instead, it's going to look at this value right here. Same for this, for this, and for this. Perfect. So now that we have this, I want to go ahead and I want to create a migration script. And I want to add a couple of scripts inside of our package JSON so that we can safely, well, push, well, generate and migrate to our Neon database. So I want to start by adding a package inside of our project called .environment. and I'm going to add it as a dev dependency here. So npm install dash capital D dot environment if you're using npm. And now I found a guide on creating migration scripts right here from Neon. So it uses schema migration with Neon Postgres and Drizzle ORM. And we're going to go ahead and skip to the bottom here. And let's go ahead and first create our migration script right here. So this is what I want to do first. So I'm going to zoom in, right? And let's go ahead and go inside of our project structure. And how about we create a new folder called scripts? And inside, let's create migrate.ts like that. And now inside of here, I want to import config from .environment, which we've just installed. And let's go ahead and run config path.environment.local like this. So just ensure that this .environment.local is the actual .environment.local that has your database URL right here. So the reason I'm doing this is because this is going to be run by outside of Next.js. So Next.js is not going to run this project. We are going to run that using either Node or Bund, right? Something like that. So now let's go ahead and let's define SQL again using Neon from Neon Database Serverless here. and let's go and call process.environment.database underscore URL, and then let's define the database, which will be drizzle from drizzle.oram.neon.http, and let's paste in the SQL. So I just like to align this like that, and then lastly, let's also add the migrate script from drizzle.oram slash neon.http slash migrator, like that. And then let's write const main to be an asynchronous method with a try and catch block. Let's resolve the catch first. So I want a console error and I want to write error during migration. And let's simply print out the error and let's exit this process. and then in the try block we're going to do await migrate pass in the database as the first options and let's go ahead and write migrations folder to be drizzle like that so this will be created every time we create a migration so let's go ahead and run this script right here so that's important perfect so now that we have this script what we can do is we can go inside of our package.json here and we can add some new scripts here so I'm going to start by actually creating db generate here and then below that we're going to have db migrate and below that we're going to have db studio so we're going to add three scripts here so first of all let's create the generate one and you can actually take a peek at this script right here. So let me go ahead and go up there. There we go. So we're going to copy this script and we can actually write it together, right? So let's see what we are going to need. We're going to need to call drizzle kit, which we've installed as a dev dependency. And we're going to use generate colon Postgres because we're using Postgres here. And then we're going to locate our schema in db slash schema dot ts. So that's why I told you that I recommend that you use the single file method for schema here, right? So this whole file will hold our schemas. Let's go back inside of package.json here. So we located our schema file here. And now let's write a dash dash out dot slash drizzle like that. So that's our out folder. So let me zoom out so you can see this in one line like this. There we go. Now let's go ahead and let's create our migrate script. So for migrate script, what we need is to run this folder right here, scripts migrate.ds. And here's the thing, you cannot run this with Node, right? Because Node doesn't support imports by default. and it also, I think, doesn't support maybe some other things here. Well, yeah, I think it doesn't support TypeScript as well. So here's what you can do. You can either simply write bun.slash script.slash migrate.ts. So that's one option. Or you can write tsx if you're not using bun, right? I think that actually give you that tip here. So let's see. After we created this migration script here, There we go. They use TSX, right? So TSX will allow you to run this script safely if you don't want to use Bun for any reason. Right here on NPM, TypeScript execute, the easiest way to run TypeScript in Node.js, right? So you can just go ahead and install that using NPM install TSX, or you can install it globally. Great, so instead of TSX, I'm going to be using Bun because I'm using Bun for this project. and for db studio we simply going to call drizzle dash kit db uh my apologies drizzle apologies drizzle like that Perfect So now that we have these three scripts right we have generate, migrate, and studio, we can run them in that order. So first things first, we have to generate from our schema some new migrations, and then we have to actually migrate them through the database. So let's try this out. I'm gonna do bun run so if you're using npm it's npm run for you so bun run database generate and there we go so it created one account one table called account with three columns right here and you can already see that migration script here so there we go we have the drizzle folder and in here we have the migration script perfect now what we have to do is we have to push that table. So it's visible right here in our neon console. So right now, if I go inside of tables here, nothing is visible. So let's go ahead and run our second script. Let's see what it's called. So it's called db migrate. So let's go ahead and run that now. Bond run database migrate. And let's see what I did wrong. So script is the wrong folder. It's called scripts multiple. Let's run that again. Make sure you don't have the same mistake as I do. And there we go. No error has been thrown, which would mean that this was successfully pushed. So let's refresh the tables here. And there we go. We have accounts with ID, name, and user ID. And now I want to try out the last script, which is to run our Database Studio locally. So let's try and run that. Bun Run Database Studio. And we have an error here. So first of all, we are missing the drizzle.config.json. And then we also need to do one more thing. So first of all, let's go ahead and let's create the drizzle.config file. So I'm going to write drizzle.config.json here. My apologies, .ts. That's what we are going to use. So drizzle.config.ts in the root of your project. And let's import config from .environment. And let's do the thing first. So let's ensure that everything we use here comes from .environment.local. And then from here, let's import define config from drizzle.get. And in here, let's export default define config. Let's target our schema to be .slash database schema.ds file. Driver is going to be Postgres. database credentials are going to use connection string to be process.environment database underscore url verbose can be true and strict can be true as well there we go so now we have our config file so let's try running the bun script again and let's see what happens there we go so we have a different error now which is the missing package pg so you can easily add that pg dash capital d So add it to dependencies. There we go. And let's do Bunran database studio again. And there we go. We can now finally see the studio. So I'm going to close this. I'm going to paste that here. And there we go. We should have our studio load any minute now. And we have the accounts with ID, name, and user ID. And we can add records from here if we want to. Perfect. So let's go ahead and confirm everything here. So I just want to show you my package JSON because we added a couple of stuff. So I have added independencies Neon Database Serverless. I've added the drizzle ORM. In my dev dependencies, I've added .environment and drizzle kit and Postgres itself. There we go. Perfect. And if you're wondering where did I get the information about building the config file from. You can find that in drizzle documentation here under drizzle kit, I believe, configuration, I think. There we go. Right here in the configuration. So it can be drizzle.config.ts.js or JSON. So we are using the TS file. That's where you can find that. Perfect. So we now have a working schema and working scripts. So there's just a couple of more things that I want to do here. So first of all, I want to go inside of here. I want to show you something. So right now, if you want to fetch accounts, for example, you would use database.select from, and then you would import accounts, right? And if you wanted, for example, to use, let's call this accounts too. There we go. So it conflicted with this variable. That's why I renamed this. So now let's say if you wanted to, I don't know, connect the relation to the accounts, right? You would need to add some inner join or something like that. So you would basically write SQL-like query. But something that Drizzle also offers you, if you want to use that instead, is, let's go ahead and find it. is it queries right here or is it, let's see. There we go. So find many, find first. So if you want to, you can use Drizzle like this. So this is querying similar to Prisma. There we go. So find many with comments. So this is how you would include a relation with the comments, right? But we're not going to do that in this project. Instead, what we're going to do is we're going to go ahead and we're going to use the SQL-like querying. So we're going to use select from users and stuff. But in case you don't like this and you yourself want to build it like this, you have to create one small modification here. And that is to import everything as schema from schema that's inside of this drizzle file here. And then you would simply add the schema here. schema. There we go. And then what you are able to do is you would write database.query.accounts.findfirst. And then you can write where, right? So this is Prisma-like query, right? But we're not going to do that in this project. We are going to use the select and then from accounts. This would be schema.accounts, right? And then we were going to do .where. And then inside of here, we would use the equals and stuff like that, right? So that's what we are going to do. So I just wanted to give you a quick tip if you want to use it differently. Great. So now that that is done, let's go ahead and practice the migration one more time. So inside of database schema, how about we add another field here, which we're actually going to need later called played ID. So let's define this as text and played underscore ID like that. So what are the steps to add this to our database? We would go inside of the terminal here. We would do bond run database generate and then bond run database migrate. And there we go. So Bound Run Database Studio should now open up and have the plate ID here. And it does. It's right here. Plate ID. Perfect. Let's refresh the Neon console. And inside of here, we have plate ID as well. And if you take a look at your drizzle here folder, you now have the change for that as well. If you ever want to, you can completely remove the Drizzle folder and then your migrations will start over again. Great. So I hope that kind of gave you a quick intro into Drizzle and Neon and how to do migrations and how to do the basic changes. What we're going to do next is we're going to go ahead and fully write our schema so that we can finally create some accounts and transactions. Great, great job. So now that we have our first table inside of our schema, we can actually go back inside of our HONO backend API and we can create an endpoint for that. So just as we had an example with books and authors, we're going to have a separate file holding all the routes for every individual entity in our schema. So right now we have accounts. So let's go ahead and do the following. I'm going to go inside of API route, route.ts right here. And I'm going to go ahead and inside of here, I'm going to create accounts.ts, just like that. And then in here, let's go ahead and import Hono from Hono like that. And let's write const app new Kono like this. And then we can write app.get slash like this. Let's get the context here. And for now, let's just return JSON accounts and an empty array here. and make sure to return this just like that and let's also just export the default app like that now let's go back inside of the route here individually and let's add app.route slash accounts and in here let's import accounts from the slash accounts so it's a default export so we have to call it like this. And we just add accounts here, like this. And I believe that we can try it out now. So let me just see if I have my localhost running or not. So bun run dev, like this. And I'm gonna go ahead and I'm gonna go inside of localhost 3000 slash API slash accounts. And there we go. So localhost 3000 slash API slash accounts is working perfectly fine. We get an empty array, which is exactly what we have defined right here. So what I want to do now is I want to enable RPC on my project here. So inside of the main route.ts here, what we need is we need to export a type called app type. And it needs to be a type of app, right? But right now, this is not exactly going to work. So as you can see in here, my app type is some weird blank schema and all information it has is the\nslash API. So what are the changes we need to do? So first of all, we have to change this to be a constant called routes. And then we're going to chain every individual route in here. So accounts, accounts, and then routes are going to be right here, type of routes. So we are still going to use the app for the handlers, but for generating our RPC type, we need this. All right, so this is an improvement because now it's not blank, right? But it's still an empty object. So something is still wrong here. So what we have to do is we have to go inside of accounts now and we have to modify this as well. So we are no longer going to use app separately here. Instead, we're going to immediately chain it like this. So immediately after new Hono, we chain dot get like that. So when you hover over this, there we go. You see the types. We have a slash endpoint with get, input doesn't exist, and output is just a never, an empty array, because that's what we defined here. And now when I hover over app type, that's exactly what we get. We know exactly the type of slash API slash accounts. So just in case you are confused about how I know about this chaining practices and how I knew to modify that in order to achieve these proper types here is because of the Hono documentation right here. So if you go inside of guides, RPC, and scroll all the way to the bottom, you have using RPC with larger applications, and they explain that you need to do that. So you need to chain directly to new HONO in this separate route managers. And you have to do the same thing in your main index or in our case, route.ts, right? So this is the routes constant, which simply chains all the routes. And then this is what we need. Perfect. So now if let's see if this is still working. So if I refresh, this still works perfectly. So that's exactly what we need. So now let's go ahead and improve the types. So what I want to do is I actually want to use drizzle and fetch the data here. So how about we do this? Let's go ahead and let's import the database from at slash database drizzle. And inside of here, I'm going to get the data by using await database.select. And let's choose ID, which comes from accounts, which we get from database schema. So accounts.id. And we need name, accounts.name. So those are the only two fields I want, right? So inside of my schema, I have ID, plate ID, name, and user ID. But I only want to return these two to the front end. And since I'm using await, I also need to turn this controller into an asynchronous method. So let's do that from accounts. And from now, let's just do it like that. So I want to return everything. And instead of accounts, let's return back data like that. So first of all, let's see what's changed. So if I refresh localhost, nothing has changed, except that now it says data instead of accounts, but it's still an empty array. But if I hover over app right here, you can see that now we have the output to be data ID string and name string. So exactly what I selected here using drizzle. And if I go over here and hover over the app type, I also have those instructions here. So that's what we achieved with this app type, end-to-end type safety. And that is going to be extremely useful for us when we combine this with React query. So we are going to know exactly what our mutation and our queries are going to return to us. Perfect. So let's go ahead and just wrap this up. So I want to properly do this. So let's go ahead and try and create a frontend hook, which will handle this slash accounts get route and store this data inside. So for that, we're going to need to set up React query. So just confirm that this endpoint works for you and confirm that you have this exact types as I do. So your app type should hold slash API accounts. The get option should have an empty input and the output should be an array of ID and name. And that's going to be in the data object. Great. So let's go ahead and let's see how to install React Query with Next.js. So right here, first, let's go ahead in the documentation and choose our way of installing. So I'm using Bun. So I'm choosing the last option here. So let me go ahead inside of the terminal here. And let's add and stack React Query. Great. Now that we have that, we have to create a provider, which is going to work with Next.js. So for that, you have to find a sequence here called, where is it, advanced server rendering right here. So you have two options. You can simply Google advanced server rendering, search it inside of the 10 stack query, or use the link in the description, or just visit my source code, and you can see exactly what we have to copy from here. So we need this initial setup. We need to create a query client provider. So it's this code right here. So I'm gonna go ahead and copy this entire code and I'm gonna go ahead and I'm going to call this query provider. So what I'm going to do actually is I'm gonna create a separate folder for this in the root of my project called providers. And inside of here, I'm gonna create query-provider.tsx. and I'm going to paste the entire thing inside. So we need useClient, right? That is true. We don't need useState here. You can leave the comments if you want to, if they help you understand what's going on here. So I'm not going to modify anything here. And I also want to write types for the children here. So I'm going to write type props here, children, React.reactNode. and I'm just going to assign the props here. And I don't want this to be a default export, so I'm just going to remove the default. So it just says export function, and instead of providers, I'm going to call it query provider, like that. And I will add some semicolons here. There we go. We now have our query provider method here. If you're unsure about the changes, you can always visit my source code. And now what we have to do is we have to go inside of our app folder and inside of our layout right here. And I wanna go ahead and wrap all of my children here inside of query provider. Make sure you import this query provider from our app slash providers query provider. So the thing that we just created here and removed the default export and we renamed this and we added the props here. So let's immediately discuss about how come this is marked as use client. Why am I wrapping my entire project in that? Won't that make all of my children use client as well? Because we had a lesson about that. Well, no, because if you pass in components through children, in that case, not all children are going to become client components. So if you pass them as children, you can safely use server components inside. So just to clear that up, no, if you wrap your children inside of a client provider, that will not automatically transform all children to stop being server components. Your children can safely be server components inside. You can read more about that in Next.js documentation. Perfect. So now that we have that, I want to go ahead and I want to create my co-located folder where I'm gonna keep all common account components, account queries, account hooks. So I wanna keep all of that together. And I'm gonna call that folder features. And inside, I'm going to create the accounts folder. And let's create API folder inside. So features, accounts, API. And let's create a very simple one, use get accounts.ts. So this is going to be the first hook that we are going to create. And this hook is going to communicate with this accounts.ts get endpoint right here. So let's go ahead and let's create that. So first of all, let's import use query from tan stack react query. And then what we have to do, which is one thing that I forgot to do actually, is we need to create an RPC client which will use this app type. So it's very simple to do that. So let's just go ahead and do this. Let's just save this file as it is. And let's go inside of our lib and let's create hono.ts like that. And then let's import hc from hono slash client. And let's import app type from at slash API, slash app API, our catch all route route. So this file right here, this is what we are looking for. And then what we have to do is we have to export const client to be HC and use the app type. And inside of here, we need a process.environment.next public app URL and add the exclamation point at the end. And now what we have to do is we have to add this inside of our environment file right here. So copy it from here. Go inside of .environment and I'm going to add it here. So let me just copy it. Next public app URL. And this is very simply going to be HTTP localhost 3000 or whatever port you are running your application on. And later in production, we are going to change this. So it's important that that's how it works here. You can write it here, of course, but you're going to have to change it for production. Great. So now that we have the client, we can head back safely inside of our features, accounts, API, use get accounts right here. And let's import the client from here. There we go. And now what we can do is client And you can see how we have amazing type safety here So we know exactly what we going to do So let export const use get accounts Let's define our query right here. So our query key is going to be accounts, multiple. And our query function is going to be an asynchronous method here, which gets the response. and it's going to be await client.api.accounts.get and execute this method. And now you can see that our response is type safe and we know exactly what it's going to return. So first of all, we don't have to worry if our route is correct. So usually we would do something like fetch slash API slash accounts, right? First of all, we don't have to worry about any typos in this because we're using type safe RPC. Second, what we would have to do is we would probably have to do something like account type and then that would be a type of fetch, right? Slash API accounts. So something like this. So we would have to define that as well. But no, we don't have to do that at all because it automatically infers that because of the way we defined our app type right here, which is extremely type safe and everything is chained and works just fine. And Hono knows exactly what data it expects back. Perfect. So now that we get the response, we still have to take care of response okay. So if not response okay, we are going to break this query by throwing a new error here. My apologies. So just error. And we're going to say failed to fetch accounts. like this. And then let's go ahead and destructure the data from await response.json right here. And let's go ahead and return data like that. And in the end, just return the query. There we go. So now we have our reusable hook here. So what we can do here now is the following. Well, first of all, you can hover over use get accounts. And in here, you can see exactly what you're going to get in the use query result. You're going to get an array of objects with ID and name. So how did I know that I can destructure data here? Well, first of all, because of type safety. If I try to destructure something else, it's not going to work because we know that we return the data object back. So if I change this to the X data, in that case, in here, we can destructure back X, right? So that's the type safety I keep talking about, right? That's why I'm so excited about this stack because I feel very confident in our code. Here's one caveat you might have thought of. So you might be thinking, okay, so what I can do is I can just wrap this into a try and catch and then I don't even have to take care of this. Remember, this is not Axios. So Axios works in a way that if it encounters an error, it automatically goes into the catch option. But this is not Axios. So in here, we have to separately take care of the error by checking if the response is not okay. Great. So now we have this very cool hook, and we can already try it out. For example, in the dashboard, in page, I'm going to go ahead and remove this. And I'm going to mark this as use client here. And I'm going to go ahead and get const. Let's call this accounts query, use get accounts, like that. And then in here, you can already see how you can work with this. So for example, you could do accounts query.data.map. You would get the individual account. And if you were to return a div here, you can safely use type safety. So account, and you can see how it autocompletes.id and inside account.autocompletes.name. So our query is working just fine. Or if you want to, you can use data. And then in here, you can write accounts, for example. And then you would skip over this and write accounts. And furthermore, you also have isLoading. So you can also use that if isLoading. And then in here, you can return a loading element, right? So that's how we are going to use these hooks. And what's cool about it is that we're going to co-locate a bunch of useful API, both queries and mutations inside of here. So each of our entities, which exist in here in schema, so alongside accounts, we're going to have transactions, for example. And then after we create all the routes for that using Honol, we're going to go inside of features and we can just copy and paste, you know, existing accounts and just slightly modify it. So we would change this later to be transactions, right? And the query key will be transactions. So a lot of reusable code and consistent code in our code base. That's why I'm so excited about this. But let's just wrap it up by doing one more thing, which is error handling. So you can, if you want to leave this or you can just, you know, I'm actually going to reset the home to be as it was. So I'm going to return the homepage. I just wanted to demonstrate how we're going to use this hook now. So all that's important is that you have this hook completed, right? And that it has all the type safety that I keep showing you. But here's a thing. So for example, if I go inside of app API and if I go inside of accounts, first of all, this is not complete. So what I wanna do is I wanna ensure that only authenticated users can see this. So I'm adding the clerk middleware from clerk next to JS server. So we learned that in the section about Hono that we can chain as many middlewares as we want inside. But I believe I shouldn't be using clerk middleware like this, my apologies. So clerk middleware, not from here, but instead from at hono slash clerk out from here, right? So just ensure you have this package installed. I believe we did that when we explored hono in one of the previous chapters, right? So in the dependencies, I have hono clerk out. Great, so clerk middleware, make sure you execute it right here. and now let's go ahead and let's get the authenticated user. So const out will be get out and pass in the context. And the first thing, we also need to get out, my apologies. So get out, there we go. So what I wanna check is if there is no out question mark user ID, I wanna throw back an error. So we can do that in many ways. First, let's try this. Let's return c.json error unauthorized. and let's write back 401 so yeah the second argument in here can be the status and let me just indent this like that so this seems fine and if we try it out so if i go here all right uh let me just refresh my page to see that it's working so if i go to slash api slash accounts. Now I should just get an empty array because I'm logged in. But if I go ahead and if I log out and if I attempt to go to slash API slash accounts, I get back an error, unauthorized, right? That seems fine. It seems like this is working as intended and it technically is. But let's take a look at what's happening now. If I revisit my route.ds and if I hover over the app type, all of a sudden you can see that my output has changed. So now my output can either be the error or it can be the data here, right? And that will cause problems inside of our hook here called use get accounts right here. So because of this right now, let's just see. So as you can see here, I can still extract the data, right? So perhaps it actually is working fine, but here's another way you can handle errors if you want to. So if you want to, you can do this. Instead of returning errors like this, what you can do is you can throw an HTTP exception. So I'm going to go ahead and import this. HTTP exception from at Pono slash HTTP exception, like that. And then in here, you can throw that instead. So HTTP, you would throw new HTTP exception 401 like this, response c.json error unauthorized and 401. Let me just see if I did this correctly. My apologies, like this. There we go. So what changed now? Well, what changed is that now you no longer have that written in here. So now the only typed response can be data because otherwise we throw the exception, right? We throw the error. We break this method. And in order for this to work as it should, so now if I refresh, it still works exactly as written, right? But we do need to do one more thing. and that is to go back inside of our route.ts right here. And just below this, let's write app on error here. Error context. And inside of here, let's check if error is instance of HTTP exception, which you can import from Honol HTTP exception here. And let me just see if I wrote this correctly. So if error instance of should be lowercase like that instance of in that case, let's return error dot get response. So we can do that because inside of the accounts here, we assign the response. So we are simply going to return this. But in case this is an unhandled error, what we can do is we can standardize our JSON output. So it never shows anything else in the API. So I'm going to write error internal server error. Or maybe just internal error. Server is redundant. There we go. So now nothing should change, but our type safety should be intact. So our app type here, C output is now intact. We know exactly what data will be returned here. So if you want to you can explore with the alternative where you return c here error unauthorized and a 401 right So I will comment this out And this still works fine but now you can see that you have two different types of output here, one being an error and one being the success message. And that can be fine, but it causes problems for RPCs because let's go ahead and do this again. So if I were to import use get accounts, and if I were to try and get my accounts query here, use get accounts like this. Now what's possible is that I get an error here as well, right? So I believe that if I try and go over accountsquery.data.map, well, it looks like it's working now, but I remember that I had a couple of issues here before because of that error. So you can decide for yourself. You can try it out. If you want to, you can use this method. My apologies, where is my accounts file? Right here. So if you want to, you can either use this. You can return C.json error unauthorized, or you can use the throw new exception, right? So I'm going to go ahead and go with throw new exception simply because I use this in my original source code, right? But if you want, you can play around, you know, I invite you to explore Hono yourself. But I found to have less type safety errors when using this right here. Basically, we're going to see whether it causes problems or not later when we actually have a chance to use this query. So for now, let's just leave it as it is. Let's turn the homepage back to homepage. I'm going to remove use client here. We don't need it. And what's important is let's recap everything we've changed here. So we added the query provider. We added the Honolib, which uses the app type and the new NextPublic app URL. Well, we added the use get accounts, which handles the error for us here. And we've also added the accounts and we've also added the on error handler here. And we added this app type here, as well as the separate accounts route here, which very simply throws all the accounts here. One thing that I want to extend this with is the where. So in here, I want to add equals from drizzle.orm. So make sure you add this. So equals. And we are very simply going to check whether accounts.userid equals the out.userid, which is currently logged in. So I only want to return accounts from this user. So this is very important. And if I refresh, I should still get unauthorized here. So I'm going to go ahead and log in. There we go. So I'm back inside. So if I go to slash API slash accounts, and there we go. I now get empty data because I don't have any accounts to my name. Perfect. So what we're going to do in the next part of the tutorial is we're going to add a post route inside of this accounts right here. And I want to keep playing around with throwing errors because it looks like there are alternative ways of doing it, right? And I just want to give you the proper information about them because when I was first developing this project, the only way I could ensure type safety was by using the HTTP exception. But it looks like HONO has a new version where it handles errors in a bit of a different way. But I still think this is going to work just fine. But as I said, we're going to see that fully in action once we actually get to use these hooks right here. Great. Amazing job. And see you in the next chapter. so just a quick explanation about our errors which I feel like might be a confusing segment before I finish this chapter so I just went into my original project and I took a look at why exactly I used this the HTTP exception and turns out it was because I had an older version of HONO So Hono has since been updated and it seems to handle these errors perfectly fine. So here's what I propose we do for this tutorial. Let's try and use this method instead because I honestly prefer it this way. So go inside of your API route accounts and remove the throw new HTTP exception entirely and just leave the simple return of a JSON object. but it's crucial that you add a 401 at the end here. So that's all I want to do. And I want to remove this as well. And let's also go inside of the route.vs right here. And let's go ahead and remove this on error also. I believe we are not going to need that. We can always bring it back if it turns out that I'm wrong. But I think with the new version of Hono, and let me just ensure and show you which version I'm talking about here. So I'm talking about HONO 4.3.2 in my case right here. So let's go ahead and do the following. I want to show you how this works. So inside of here, inside of features API use get account, in my old project, if I used this type of JSON error, I would get an error here. It would tell me that data doesn't exist on this response because, well, there's no data in this response. And I believe if I remove the status 401 and save this, I get that exact error here. Data does not exist on type error. So that's the error I was getting. But in the older version, just adding the status did not fix that. But now it seems to fix the types entirely. And that's great. So Honol was improved by the time I finished my course, well, not course, but my initial code to the time where I started recording this. So this is great. We can actually just use this. You can just set the status 401 for the error. And that will automatically in this response ensure that there is a separate client response for the 401 status. And there is an entirely different client response for all the other statuses here, which has the proper data. And also what brings me confidence here is that if we don't handle the error, we also get this TypeScript error. So this handling of error gives me confidence with this type safety to tell me, yes, you handle this error properly. All you have to do is take a look at the if response is not okay and just throw the error. And React query is not going to attempt to destructure this if there's no data in here at all. So I believe that we can just do this safely. So let's go ahead and do it like that. if we encounter any problems, we can easily bring back our HTTP exception. Great, great job. All right, so now that we have our API endpoint for fetching the accounts, I want to create an endpoint for creating an individual account. And we will start that by revisiting our database schema right here. So what I want to do is I somehow want to have one schema that will be generated using this table right here. And then I want to reuse that schema everywhere. And luckily for us, there is a package called drizzleZod. So head inside of your terminal and do bun add or npm install drizzle zod and this will add that package. So now that we have that go ahead and add it in your schema file. So you will import this as create insert schema from drizzle zod like that and then what you can do is write export const insert account schema. Create insert schema and simply passing the accounts like that. There are, of course, more options than just this. Sometimes you will have to slightly modify the schema as to opposed what's in the database, right? So for example, you can take a look at the documentation for drizzlezod and in here you can see that you can just create the schema like we did. You can also create the select schema or you can also override specific fields if for any reason you want to change the role to be a number and you defined it as text in the table, right? So you can play around and it's very powerful and you can also add some useful refining here, right? And you can also do pick and omit all of those things. So in my case, I want to keep it exactly like this and then perhaps I will change it later on. Great. So now that we have our schema here, let's go ahead and let's create our post request. So we're going to go back inside of app API inside of the accounts here. And I'm going to close the get request. Actually, I'm not going to close it just to not confuse you. So this is what I'm doing. I will remove the semicolon and I will simply chain the post like this. So that's what I am doing here, right? But I will close get now just so, you know, I have more space to work with. So let's go ahead and collapse this elements here. Let's go ahead and get the context. Let's mark this as asynchronous and let's return c.json and an empty object here. And I'm going to add the clerk middleware here. And then I'm going to go ahead and do the same thing that I usually do is get the out using get out and passing in the context. If there is no out user ID, I will return c.json with an error unauthorized. And important, give it a status of 401. So we have proper types here. And now what we have to do is we have to validate using Zod what kind of JSON can this post request accept And thankfully for us we can do that by adding another middleware here called ZValidator And let just see where can we import the Z validator from So let import Z validator from slash Hono Zod validator There we go So we have the Z validator here And what are we validating? Well, we are validating JSON input here. And what is our schema for that? Well, insert account schema, which we've just created from database schema. And here's the thing. So the insert account schema will accept the ID, the plate ID, the name, and the user ID. So user ID is required and name is required. But here's the thing. I don't want user ID to be passed through the JSON object. I'm going to assign the user ID using this right here when I create it. That's how I'm going to do it. So I will tell the front end, okay, I want you to use the insert account schema, but only specific fields. You will take care of the name field. That's what the front end form is going to take care of. So that's how easily we can modify this reusable schema if, as I just showed you, sometimes we need a bit of a different object to work with. and then what we can do here is we can get the values using c.request.valid json inside of these values we are going to just find an object with the name string you could also if you want to destructure that specifically like this name there we go but i'm going to work with values here and then what i'm going to do is very simply create this so i'm going to write const data and I'm going to write await database.insert accounts.values and inside of here, I'm going to open an object. So here's what I want to do. So I can assign the user ID to be out.userID. I can go ahead and spread values, which will take care of the name field. We don't need to pass in the played ID, but we do need to pass in the ID. So id is currently defined as a very simple text string, which gives us complete freedom onto defining the id however we want. So if you want to use uuid, you can use that. If you want to use nanoid, you can use that. I'm going to be using cuid. So for that, we're going to have to install this package right here. So let me expand this. There we go. So it's parallel drive slash cuid2. If you want to, you can use UUID v4 or something else. So I'm going to copy this and I'm going to use bun to add it to my project. So let's go ahead and do that inside of terminal here. Bun add CUID v2. And now what I can do is I can import from that in package, create ID like that. and then I'm gonna use that right here. There we go. And make sure you execute that. And that will create me a string, which is all the validation I've put here so far. You can also chain this with, for example, a default function. And then inside of here, you can use the create ID, for example. But I haven't tried that out. I think it can work, but I don't know how it works with our migration script and all of that. And honestly, I'm fine with doing it like this. it gives me explicit control over ideas. But, you know, you absolutely can automate that in Drizzle, right? Don't worry. You can read the documentation. And here's another thing you ought to know when working with Drizzle. So by default, the select script or the select function will return the data, right? And it's going to be an array. but the insert method by default will not return anything right so this data actually isn't my newly created item if I attempt to do data dot something you can see that this is not my object so what you have to do is you have to chain dot returning like this and if you only want to return specific things, you can go ahead and specifically write, for example, name. I believe this is how you do it, or maybe not. But basically, you need to add dot returning. And now when you hover on this, there we go, you get an array of created items. So you would go ahead and return. You can either destructure the data immediately here, like that. And then inside of here, what you would do is I simply return the data, the single new created element like that. So yes, when using drizzle and when using select, you're always going to get multiple. You're always going to get an array. There is no, I'm not sure what would be the equivalent, dot single, right? Or dot unique. That doesn't exist. That's because they are staying true to SQL. right? So in SQL, you would always get an array of items. So that's why they are always providing you with an array of items. So whenever you need a single element and you know it's going to be unique, you can just immediately destructure it like this. And then this would be the first item in the array, right? Don't do that for the get route. So I'm just demonstrating on the get route here. So let me close it. Get route stays unchanged. We are only working on the post route here. So yeah, in here, you can already destructure it. If you feel like this is unsafe, what you can do is you can do data like this, and then maybe data first in the array, if that is safer for you. But I'm going to be doing this. And that's pretty much it. So this is exactly our post endpoint for creating something, right? So what we have to do now is we have to create a form, which will be able to, well, create this kind of value. So when I hover over post, you can now see that inside of here somewhere, let's see, what if I remove the get? All right, so I thought that somehow I will be able to see what I'm accepting here, but there's just too much stuff happening for it to be displayed in this small box, but we're going to see that in a moment. All right, so what I want us to do next is I want us to add form elements from ShadCN UI. And we can do that by using a script here. So bunex ShadCN-UI latest add form. So what this is going to do is install a bunch of different stuff that we need. So let's go ahead and take a look. So in package.json now, you should have hook form resolvers. you should have the react label you should have react hook form installed and is that it i believe that could be it and i think now we have to individually add a couple of more fields so let's see so the ui folder is everything that chat cn has added so we have button form label and sheet and we also need to add one more thing bunx chat cn let me clear this so you can see bunx chat cnui at latest, add the input. That's what we need. Let's do bun run dev. Perfect. So now what I'm going to do is the following. I want us to go ahead and I want us to create a new account sheet component. So let's go ahead inside of our features, accounts, and let's create a folder of components. So we're going to co-locate our account items here. So both the hooks and the components, everything's going to be here. Instead of here, let's create new-account-sheet.pss. And let's go ahead and simply create that component. So let's import everything we're going to need from as slash components, UI sheet. we need the content we need the description we need the header and we need the title like that great let's go ahead and let's define this method so expert const new account sheet like that and let's return this component right here so inside of here we're gonna need sheet content let's give this an open. For now, let's make it true, right? So I want to keep it open. Last name here, space Y4. Inside of here, let's add the header. Let's add the title. And the title can very simply say new account. And below that, we can have a description, which can say create a new account to track your transactions like that and we don't need anything more so obviously we're going to add a form but for now I just want to render this component right here and let's go ahead and do that inside of a new provider, which we are going to create here. It's going to be called sheet provider.tsx. And let's go ahead and do the following. So let's go ahead and mark this as use client. And let's export column to sheet provider here. And let's render inside of a fragment, because we're going to have a lot of those new account sheet like that directly from add features, accounts, components, new account sheet like this. And now let's add this inside of our app layout right here. And we can just add it inside of the query provider above the children. So just make sure that you've added it from our new providers folder. And I think that just by adding this, if I go ahead inside of my local host here, I think it should already be opened, if I'm not mistaken. Let's see. Or perhaps not. Let me refresh one more time. There we go. So now you can see that when you refresh, you have this sheet opened right here.\nhave a hydration error here. So all of that is fine. We're going to resolve all of this in a second. So first of all, let's resolve this hydration error. And we can do that by going inside of the sheet provider here. So as I explained to you before, just adding use client will not mean that something is not rendered on the server, right? So this hydration error is happening because we have a mismatch between what rendered on the server and what rendered on the client. And that's because on the client, this turned to an open sheet, right? So what I'm going to do is I'm going to find a way to only render something on the client. And models are perfectly fine for that, right? There's no need to server-side render models. So I'm going to go ahead and I'm going to import use mounted state from React use. So what we can do here is write const is mounted to be use mounted state. And then I'm going to write if is not mounted return now. And now I'm going to explain to you how this works. Seems like we are still getting an error, but we're going to go ahead and resolve this later. Right. So let me first explain to you what the use mounted state does. So use mounted state is the equivalent of this. const is mounted, set is mounted, use state, false by default. Let me comment this out. And then another use effect from React, which calls set is mounted to true. So these two lines are the equivalent of this one line, right? That's why I'm using this because we already have this installed. It's very useful and we are using it for media queries, right? So why does this work for fixing hydration errors? Well, it works because this set is mounted can only be called inside of useEffect. And useEffect or the component did mount lifecycle, which has the empty dependency array, can only be called once the component has been rendered on the server and then it's finally on the client. So that's why this solution works. So until that happens, we don't even attempt to load this stuff, right? So let's go ahead and remove this now. Or you can use this solution, whichever solution you like. I think this is pretty much cleaner. Great. So what I want to do now is I want to go ahead and add Zustand inside of our project. So let's do bun add Zustand or Zustand. I'm not sure how to pronounce that. and what we're going to do now is we're going to create a kind of a controller for our drawer here to create a new account so let's go inside of features accounts and let's create a new folder called hooks and inside let's create use new account.ds let's go ahead and import create from Zustand. Let's create a type new account state to have is open to be a Boolean, on open to be a void, and on close to be a void as well. And let's export font use new account here to call create with the new account state here. Go ahead and extract set, which immediately returns an object. So make sure you wrap it inside of parentheses, right? So this and this is not the same. This is a method. This is an immediate return of an object. So let's define isOpen to be false, onOpen to be a method which calls set and it will change the object isOpen to true. And then we're gonna have onClose, which does the opposite and sets it to false. There we go. So now what we can do is we can go inside of our components, new account sheet here, and we can import use new account from hooks, use new account, but I'm going to change this to features accounts. So I like to keep my imports like this. I'm going to keep the features always above other components like that. And in here, we can extract is open and on close. So I will map the open to not be hard-coded, but instead be controlled by this. And this will be on open change on close like that. So now if I refresh, this should be closed by default. It should not appear anymore. So what we can do now is the following. We can copy this hook and we can go, well, we only have the dashboard page.tsx. So let's use that. So let's mark this as use client, the homepage, right? And let's add this and let's import use new account. From features, accounts, hooks, use new account. And let's add a button here from components UI button. Add an account. And on click is going to be, let's go ahead. And the only thing we actually need is on open here. And let's call on open. There we go. We are going to change this later. Obviously, it's not going to be open from here, but we need a button which will open something. And now when you click here, there we go. And when I refresh, there are no hydration errors anywhere. So all of this is now perfectly controlled. So why did I do all of that? Well, I did it so that we have this useful hook, which we can call absolutely anywhere in our project. And no matter how we want to open a new account form, we can do it using that hook. I think it's very useful. It has a nice global control and it doesn't cause too much problems inside of our project. Great. So now that we have that, I want to do a slight adjustment. So when I am on desktop, this is fine. I want it to be like this. But when I am on mobile, when I'm on small devices, I want it to fill the entire screen, right? No need for it to be a half screen. So let's go ahead and do that. So let's revisit our Sheet component. So I'm going to go inside of app, my apologies, components, UI, Sheet component. And let's go ahead and let's find the variants right here, Sheet variants. So first thing I'm going to do, I'm going to find my default classes here. and I'm simply gonna add overflow Y auto. So I wanna make sure that people can scroll in these drawers. Then I'm gonna find the left right here and I wanna slightly modify it here. So instead of using the width three fourths here, I'm gonna use the width full and then I'm gonna modify this so it doesn't look at the small breakpoint, but instead the large breakpoint. And I wanted to use max with MD like that. So let me go ahead and show you like this so you can perhaps notice the changes better. So what I added is the sheet variance constant. I added the overflow Y auto in the default classes And then I found the left. And in here, I changed from width three quarters to width full. And I found the breakpoint for SM to LG. If you're unsure, you can just copy this from my source code. And this slight change, I believe, should be enough. So right now, there we go. It looks fine here. And when I go into tablet or mobile mode, it should become full screen. But it looks like it's not. Let's see, am I still making a mistake here? All right, so it looks like I still have to debug. Looks like something is not right. Well, I made a mistake because I modified the left when I should be modifying the right. So let's go ahead and do the same thing here. So find the right and change from with three quarters to with full and change this to use the LG breakpoint max with MD like this and there we go now it has the effect that I want so on tablet and mobile I wanted to behave full screen but on desktops I wanted to just take a bit space on the right and when it collapses there we go I want it to be full width and let me just resolve this back for the left side so I'm going to change so in the left I'm going to change this to three quarters. And I'm going to change this to be a small breakpoint and SM here. There we go. So let's see the final changes again. So left stays untouched. We only modify the right by changing this to width full instead of width three quarters and changing the SM breakpoint to max with SM to large max with MD. And I also added the overflow Y auto. There we go. So now we have this as per my liking. And now it's time for us to create our reusable account form. So let's head over inside of our features components. So inside of the accounts, right? But that's the only one we have. And let's create our account form. So we're going to reuse this both for creating a new one and also for updating one. So let's add all the imports we need. So we're going to need z from Zod. We're going to need the trash icon from Lucid React. We're going to need use form from React hook form. Zod resolver from at hook form resolvers Zod. we're going to need the input component from components UI input we're going to need the button component from the same place we're going to need our insert account schema from database schema and lastly we're going to need all the add slash components UI form elements so that's the form form control form field form item form label and form message like that So these are all the imports which we are going to need Now let define our form schema So const form schema is going to be insert account schema And we are only requesting the name here. So that's the only thing we need. This form will only be for creating the name of the account. And now let's create that into type called form values, which will be z.input type of form schema. There we go. And now let's go ahead and let's create a type props here. So it's going to accept an optional ID in case we are editing an account. It's going to have optional default values, which can be a type of form values. It's going to have an on submit method, which accepts the values, which again are a type of form values, and it returns back a void. and it's going to have an optional on delete, which can be a void as well. And disabled will be an optional Boolean. Let's export const account form and let's assign these props right here. So inside of here, we can destructure ID, default values, on submit, on delete, and disabled like that. Great. And now inside of here, we can return a form like that. And in order to get rid of the error here, we need to define the constant form using use form. And let's give it the type of form values here because we know what we are working with. Let's add a resolver to be Zod resolver form schema. So we have form schema defined above right here, right? And then let's pass in the default values to simply be default values like that. And let's go ahead and let's create const handle submit, which accepts the values, which are a type of form values, which for now we can just const log like this. And let's const handle delete here. For now to just console log, or we can actually already do this. We can call on delete, but since it's optional, we're going to add a question mark and only then execute it. So this is actually safe. Right, so now what we can do is we can spread the form inside and we can add a native form element right here. And let's give it an on submit here to be form.handle submit and pass in. Whoops, form. dot, it is, yes, handle submit and passing the handle submit method again. So this is from the form constant and this is our handle submit. So using this wrapper around our method ensures that we only trigger this once it is validated, right? So no invalid fields are passed. And let's give this a class name of space Y4 and space Y4, oops. and padding top of four. And we can collapse these two like that. There we go. And now inside of the form here, let's create a form field, which is a self-closing tag, which accepts the name to be, well, name. How do I know its name? Well, because that's the only field we are controlling. So yeah, it's a coincidence that the prop is the same thing as the value, right? But this refers to this name right here. That's why this is controlling the name field. We need control to be form.control. And we need the render to be a method which immediately destructures the field. And then it calls, not calls, but it returns a form item like that. And then we have form label, which will simply render name like this. And below the form label, we're going to have form control, which will use the input. The input will be disabled if the global disabled prop is being passed. And we're going to have a placeholder to explain to the user what we expect them to name this account. For example, cash, bank, credit card, something like that. and then we're going to spread the field. So what does spreading the field do? So inside of field, this very cool library use form handles all the event handlers for us, right? So usually you would have to have on change, right? And then inside of on change, you would get the event and you would set value to event target values, right? Something like this. But you no longer have to do that because a field does all of that. So when you spread the field, let's take a look at it. There we go. So inside of here, you have on change, on blur, value, disabled. So all of those things are being passed here. So I override the disabled on my part. Great. I think this is enough for us to, well, it won't work yet, but I think we can already render it. So let's go inside of the new account drawer here. And below the header, let's add the account form. From dot slash account form or features, components, features, accounts, components. There we go. So I'm going to add that. And it requires a couple of fields here. So I'm going to give it on submit for now to just be an empty arrow function. And disabled is going to be false. So right now, if I go ahead and refresh the whole thing and click add account, there we go. I have my form here, which allows me to write the name of my new account. Perfect. So let's go ahead and let's add a submit button here. So let's head back in the account form. And let's go ahead and still, let's just go outside of here. So still inside of this native form element, right? Add a button component, which will check if we have the ID. If we have the ID, the label is going to be save changes. Otherwise, it's going to be create account. Class name is going to be with full. And disabled if the global disabled prop is passed, like that. and then below that we're going to check uh well let's go ahead and let's uh render it first so you can see what it looks like so let's add a trash icon here and let's write delete account let's give this a class name of size four and margin right margin right of two like this and let's go ahead and give some props here. So type here is going to be button. Disabled is going to be disabled. On click is going to be handle delete. Class name is going to be full width. Size is going to be icon. And variant is going to be outline. So my apologies, size is not icon, right? and let me just see why is this not working so mr2 this should be pushing this or is it mi2 let's see that's a bit odd let's just see mr how about br2 all right uh just a second so i can debug this all right so i'm gonna leave this for later uh it might be due to a different lucid react version i'm not sure why usually in all of my tutorials margin right to always safely pushes the content let's see what if i wrap it inside of a paragraph will that push it in? How about a div? Now, okay, so I'm going to debug this later. Let's go ahead and let's focus on wrapping this up. So what's important with this delete button is that it has a type of button because it is inside of a form element. So if you don't specify which one it is, it will count as submit inside of this form. And now what we're going to do is we're only going to render this button if we have the ID like this. And what I recommend you do when you use this kind of conditional is that you turn the ID into a Boolean by adding double exclamation points like that. There we go. So you can see that now it disappeared because we don't have ID in this case. Right. And you can, if you want to use this kind of syntax, if that's something you prefer more like this. Let's see. There we go. All right. So what I want to do now is I want to go ahead and I want to pass in the actual on submit method here. So let's go ahead and use that. Let's pass in the on submit and let's pass in the values like that. So we should have the on submit right here. There we go. Let's go back inside of the account sheet here. And now what I want us to do is I want us to create the on submit here. So const on submit will accept the values, which again are going to be a type of form values here. So let's go ahead and let's just copy and paste exactly what we had in the account form. So this too. Right here. insert account schema from database schema and z from zod so i'm gonna move this here and i'm gonna move this all the way to the top like that there we go so now we have form values here and let's console log values in here and now pass this on submit right here there we go let's try it out so I'm going to go ahead and open my let's go ahead and do this I'm going to open my inspect element here and I'm going to name this account test let's see what the errors are all right we are having some errors we're going to see where this comes from and there we go the values are name of test perfect so this seems to be working just fine Great Perfect And I believe that this error for the controlled input might come because we don have default values here So how about I go ahead and do this? If I pass default values, name, it just be an empty object. Let's see if that will help resolve that bug. So let me go ahead and test. There we go. So now we don't have that. And this is still working. Perfect. So now what we have to do is we have to submit this to our newly created endpoint in the accounts, right? So we have the get request, but now we need to call this post request. So for that, we're going to go ahead and we're going to create a new hook in our features. So let's go inside of features API, and we're going to call this use create account dot DS like this. And let's go ahead and let's add a couple of things from HONO. So let's import infer request type from HONO. And let's add infer response type from HONO. And let's go ahead and let's import use mutation from 10 stack react query. and let's add useQuery client from 10 stack React query. And finally, let's import client from libHono. Let's define the response type of this mutation to be infer response type, open pointy brackets, type of client.api.accounts.post. So this is the response type, either an error or data right here. Let's define type request type to be infer request type type of client API dot accounts dot post, but we are not done yet. So the request type is what this endpoint is expecting to accept in the endpoint. So what we need is to get this, this validator right here. We can do that very simply by targeting JSON. So let me zoom out so you can see it in one line like this. Can I zoom out this even more? I can't. Okay. So that's what we need. And now let's go ahead and let's export const use create account. Let's define the query client, use query client. and let's go ahead and let's define the mutation. So const mutation is going to be use mutation, open pointy brackets, and let's go ahead and let's fill in the values. So let's add the response type as the first argument. Then let's add an error type, which we have natively. You don't need to define that anywhere. And then the request type, like that. And then open an object, sorry, a function. Let's add the mutation function here. Asynchronous JSON. So what is JSON? JSON is what we are going to send from our form. And you can already see the type safety here. How do we have that? Well, because of the request type, which we have added as the third argument here. And this should not be, this should be this, right? Like that. Now inside of here, let's get the response using await client API accounts.post and simply pass in the matching JSON and return await response.json like that. There we go. And then you can add on success here. and inside of here if we successfully created it what I want to do is I want to use the query client and I want to invalidate all the existing queries with the query key accounts what this will do is it will refetch all accounts every time you create a new account so your user get accounts here will get refetched because a new one was just created so that's how we can do that here And you can also add on error here, for example, to do something. So how about we do that? Let's go inside of our terminal here. And let's do BunX, ShadCN, UI, LatestAd, Toaster, sorry, Sonner. Let's do BunRunDev again. Let's go inside of our app. Where is it? Layout. Let's go ahead and let's import Toaster from components UI Sonar. And let's add it just below our sheet provider here. That's all we have to do here. Then head back into the use create account hook. And at the top here, let's import Toast from Sonar. And now on success, let's add Toast.success account created. And on error, let's add toast.error failed to create account. There we go. And what we have to do is we have to return the mutation in this hook. And this is now ready. So let's go inside of our components new account sheet right here. and below this, let's add the mutation from use create account. So I'm going to change this import to be consistent, features, accounts, API use create account and let me move it up here. So this is all in one line, of course, like this. And now what we can do here is very simply do mutation.mutate and pass in the values. And as you can see, the TypeScript perfectly matches all the way from our form values here, from the form values which are being passed in the form, all the way to what our API endpoint expects. So that's why I love this text stack so much. And the thing about this is that this is not a promise. So you cannot do this. If you really want to, then you want to use mutate async. but you don't have to because for example after this finishes alongside this on success here I also want to close the account sheet so I can do that by simply adding an object and I'm going to continue with the on success here and call the on close just like that and now what we can also do is we can disable everything while it's mutating so mutation dot is pending like this. Let's go ahead and try this out now. So I'm also going to go ahead and open my database studio here. Actually, we don't have that because we have an API endpoint, right? So let's write test. Let's actually refresh everything. So it's safer that way. So let's write test. And let's see. There we go. Account created. And if I go ahead inside of localhost 3000 API accounts, there we go. We have an ID and name test. So it's perfectly working. Let's go ahead and let's notice the network tab to see what's happening here. So I'm going to go ahead and zoom out a bit on everything. So I'm going to write test here. So let's see what's going on. So we're calling the accounts API endpoint with this payload and this is the response, right? So we have a user ID, name, test, but we are not returning played ID well because we don't have that, right? And when I refresh here, now we have two of those. Perfect, so our integration to create accounts is working flawlessly and we can now easily open this form from anywhere and this form will be reused to edit the account which is something we are gonna do next. Great, great job. So now that we have some routes and some hooks to manipulate the accounts entity, I want to create the actual accounts page. So to remind you, when you expand on your desktop mode here, we do have the accounts page. So how about we change this from being a 404 page into actually rendering accounts and making this button displayed there instead. Let's go ahead and do the following. I'm gonna go inside of my project here and let's go inside of the app folder and let's go inside of dashboard. Inside of here, I'm gonna go ahead and create a new folder called accounts. And inside, I'm gonna create a page.dsx. Let's go ahead and write accounts page here. And inside of here, I am very simply gonna return a div, which will say accounts page. And now when we have this, if we try and going either manually to localhost 3000 slash accounts, there we go, we have the accounts page. And you can also see how in here the accounts is now highlighted. So I can now switch between overview and accounts. In case you're having errors here, make sure that inside of your navigation component, you didn't misspell anything in the routes here. So accounts should go to slash accounts. Make sure there are no typos between this and your folder name here. Great. So now that we have the accounts page here, let's go ahead and let's add kind of some styles and kind of a card look to it. You're going to see what I'm talking about in a moment. So I want to use the card component. So let me go ahead and wrap this entire thing in a card. And let's go ahead and import everything we need from card. So import from add slash components UI card. Or we don't have card. My apologies. So I kept thinking that we have the card. So let's go ahead and do Bonex chat CN UI at latest. Let's add card like that. There we go. So now that we have the card we no longer have an error And now we can import card card content card header and card title There we go So this is going to be the card, but then we're going to have card header here inside. And inside of card header, we're going to have a card title. Let me just refresh this page because it shut down the server. and now we should start seeing active updates here. There we go, see accounts page. And I just want to style it a bit. So for the card, I want to give it a class name of border none and drop shadow small. For the card header, I want to go ahead and give this a gap Y of two. I want to give it on large devices a flex row. On large, I want to give it items center. And on large, I want to give it justify between. That's because in this card header, alongside card title, we're also going to have a button. But before we add the button, let's go ahead and style the card title by changing the text to be extra large and line clamp one in case it gets too large. Great. And now below that, we can add a button component, which comes from component UI button. And in here, we can add new like that. So let me just refresh this. There we go. So this is how it looks like on mobile. But when you expand, this is how it looks here. And if you want to, you can also add the plus icon from Lucid React. So let's give it a size 4 and margin right of 2. There we go. Add new. Perfect. And I also want to give this a size of small. There we go. and let me just confirm that this takes the full width on mobile. It does. Perfect. Exactly as I want it to look like. And now what I want to do is I want to go ahead and I want to add the const new account from use new account. We can import that from features account hooks use new account. So I'm going to keep those at the top here. And since this is a hook, we need to mark this entire page as use client to get rid of that error. And I'm going to go ahead and give this button a non-click to use new account dot on open. There we go. So now when you visit the accounts page and click add new, this is where you can create your accounts. Perfect. So what I want to do now is I want to add a kind of a negative margin to this card. So it takes this empty space here. So it looks a bit better. So we're going to do that by giving you the effect on this div right here. Let's go ahead and give it a class name. Max with screen to Excel, MX auto. So the first two classes are so it doesn't expand too much, right? So when I zoom out, it will stop expanding at some point, the same as our header, right? Without this class. So if I remove this, then you can see how it expands all the way here, which is not something we want, right? We want to keep it contained in the center if the screen gets too wide. So that's the first thing. And then what I want to do is I want to add a full width padding bottom of 10 and minus margin top of 24 like this. There we go. So now this card is kind of above the space here. And don't worry, it's going to get bigger because we're going to add the data table here below. So then it's going to look better. Great. So what I want to do now is I want to go ahead and I want to build a data table component. So ShadCN UI has the data table component and it's built using TAN stack table. So in order to get the data table, we actually have to install a couple of separate packages. First one being the actual table component from ShadCNUI. So I'm going to go ahead and use Bun for this. So this is just, you know, installing the presentational table. So let's do Bun. Actually, let me copy and paste this. There we go. So Bun ShadCNUI latest add table. If you want, you can use this prefix. I don't know. There's no difference for me if I use it with or without that. There we go. So we have that. But we're not done yet because we also need to add 10 stack react table like this or npm install 10 stack react table. Great. So once we have that, we are ready to start building our data table here. So we're going to keep that in the accounts page for now. Actually, we're going to keep the data table component itself in the common components, but we're going to have some columns and actions, and we're going to keep that here in the accounts. You're going to see why in a second. Right. So these are some prerequisites here. So this is how our dummy data is going to look, and we can work with that data. So this is what we're going to do. We're going to keep the data table in the common components, and we're going to keep the columns in our, well, their use payments. For us, it's accounts, right? So because columns are not exactly reusable, they are specific for each and every entity that we have. Right. So let's start by defining our columns. So I recommend that you open this ChatCNUI documentation here and choose the data table and find the first step column definitions. Or you can copy stuff directly from my GitHub if that is easier for you. So inside of accounts here, I'm going to create columns.es. and I'm just going to paste that here. There we go. So this is some dummy type here that we are going to use. So we are pretending that we are loading the status, email and amount, right? We know that we have some different fields in our database, but it doesn't matter for now because we just need to get this working. Great, and now we need the actual data table component. So I'm gonna copy the entire thing from step two and I'm gonna create the data table inside of components here. So reusable, datatable.tsx. And I'm going to paste the entire thing here. So just ensure that you have 10 stack React table installed and components UI table right here. And there we go. Just by itself, you should not have any errors here at all. You should just have a data table. Right. Let's see what is step three here. So the step three is to render the table. So for that, we need some mock data. So let's go ahead and do the following. So they do it this way. Let's see if we're going to do it like that, or are we going to do something else? So I'm going to go back inside of dashboard, accounts, page, and I'm going to paste this method of theirs here. And we can import the payment from .slash columns, because inside of columns, we export the type payment. So I'm doing this inside of page, right? But we can't exactly call this function anywhere. So here's what we can do instead. Perhaps this is going to be easier. So let's go ahead and just copy this array like that. And inside of here, let's define const data. And let's make it that array and this single object inside. And then I'm going to go ahead and use this payment, which is an array as the type of this. Like that. There we go. So then we don't need this asynchronous function at all. And we just simplified stuff. I'm going to go ahead and move this to the top. I'm going to separate this because this is a different kind of import. Now that we have the data here, we are actually ready to try out our data table component. So I'm going to go ahead and copy the data table as they are using it here. And below the card header, we're going to add card content. I believe we already have card content added here. And we're going to add the data table. Now let's fix the errors. So first of all, we need to import the data table from add slash components data table. I'm going to move here alongside with the button here. The second thing we need is to import columns itself from dot slash columns. They are right here. So we can keep that together here like that. So just ensure that when you copy it, nothing changed and you still have the export const columns like that. So I believe just by itself now, this should be fine. So when I go ahead and refresh this, I should see, I believe, one element here. There we go. So pending, email, and amount. Great. So this looks fine, right? But now what I want to do is I want to make sure that this table has, you know, some more actions here. So the first thing they teach you here is how to format inside of your cell, right? So if, for example, you want this amount to change from the plain integer 100 to always be a type of dollars, right? What you can do is you can go inside of columns right here and find the amount. And you can manually change the cell to be some type of component. And we are going to do that later. But for now, I just want to add more functionality to the table itself. So I'm going to skip step two, which says update columns definition. We can skip that. Instead, let's go ahead and also skip row actions. We don't need that as well because all of that requires separate components and stuff. You can see that we're going to have to copy a lot of things. Let's start easy. Let's do pagination. So inside of the data table documentation, find pagination and follow the steps. So we're going to be working inside of data table components. So you can close everything else from now. Find pagination and first import get pagination row model from 10 stack react table. I'm going to go ahead and add that here. After that, you're going to go ahead and you're going to add the pagination row model. Let me go ahead and we copied this. And now we're going to add that inside of here, inside of use react table, just below get core row model like that. Now we have to add the pagination controls. So I'm going to import the button component right here. And we have to wrap our entire component in a div here, right? So find where your component starts in the data table and wrap it inside of a div. And go ahead and go to the end of the component and add the end of the div. Then you can select everything and indent it inside. So this is some weird error if I just reload my window\nSo it's going to go away. There we go. Now what we have to do is we have to add two buttons at the end of our table component, right? So where it ends. Actually, at the end of this div, right? So go ahead and do that. Inside of this code, you can find it here. So I'm going to copy the entire thing. You can see it's highlighted. And don't copy the last div. You don't need to copy the last div because we added it manually when we added the first div. So go right here and simply paste those two elements. There we go. So no need to copy that additional div. If you did copy it, you're going to end up with this. You're going to have an extra closing div. There we go. So now you can go ahead and expand this back. And this is how it should look like. Right here at the end of the table and then at the end of the div, you open a new div and add two buttons. Let's see how this looks. So inside of here now, you should have previous and next, and both of them should be disabled because we don't have enough data to trigger the pagination. Great, so pagination is working. Now let's add sorting. So it's going to be quite similar. We are still working inside of data table itself. First of all, let's import everything as React here. then let's add the sorting state below the column definition and let's add get sorted row model right here then let's go ahead and add a use state here when where our component begins just above the table constant so let me zoom out so you can see this sorting set sorting react use state we give it a type which we've imported from here and we define it by an empty array at first. So I'm going to zoom in now. And then let's go ahead and let's go inside of use react table. Let's add on sorting change to be set sorting. And get sorted row model is going to be get sorted row model, but execute the method. And state is going to be an object with sorting inside. There we go. And by now, there is nothing that will change here, but the table can now handle the sorting functionality. What we have to do now is we have to visit our columns. So let's go inside of accounts, columns right here. And let's go ahead and do the following. So I'm going to import from Lucid React, arrow up and more horizontal as they did here. So arrow up and more horizontal. and we're going to find for example the email field and we're going to change the header so header right now is email if I change it to email too you can see that in here it changes to email too so we're going to turn this into a button which will trigger either the sort by descending or ascending so that's exactly what we can do here we can copy this header right here like this and then replace it with this and let's see what we are missing looks like something is wrong here oh okay so what's wrong is that the columns is named columns.ts in order to use javascript inside or jsx more specifically we have to change from ts to tsx and now we have a proper and working syntax but we are missing one closing bracket here. There we go. And we also need to import the button component from components UI button like this. And I think we don't need more horizontal. Yes, my apologies. There we go. So let me just add this imports here. And now if you go ahead and try and go inside of your data table, let's refresh now to see if we still have these errors. Yes, it seems like we have an error now inside of page.tsx because the hot reload is probably confused because we just renamed this from .ts to tsx. So let's try and first saving like this. So save this and get an error again and then try and find columns again and save again. And there we go. Now it works. Or you can just shut down your app, right? So now you can see that we can, well, we don't have enough data. So how about we do this? Let's go ahead and add another data here and let's change this to be a dot example. And let's go ahead and change some numbers and some statuses so we can notice the difference here. Okay. So I have to use success here. Okay. Like this. There we go. So now if I try and click email, there we go. You can see how it can sort by email. And that is exactly what I wanted us to do here. So we just need one example of the header here, and then we're going to copy it everywhere else where we need it. Right. So if you're still getting that error for columns, you can just shut down your app and run it again. Right. So let's get inside of data table and now let's add filtering or, well, searching, right? So we are going back inside of data table component itself. So I'm going to close everything else. I'm going to import column filters state right below column def. Then I'm going to import get filtered row model just above the pagination. And I'm going to add the input component just below the existing button component input. Then I'm going to copy this use state from the example, and I'm going to add it just below our existing sorting state. So let me zoom out just a bit more so you can see. So column filters, set column filters, react.use state with column filter state, and it has the exact same thing as one above, an empty array as the default value. And now let's go ahead and let's add on columns filter change. We're going to get that. Where should we add it? Let's add it right below here. On column filters change. And then get filtered row model. And then we have to modify the state to also have column filters. Like this. And then we already wrapped our component in a div. So all we have to do is copy the highlighted code here, which is a div and an input inside. And we just have to add it above the rounded MD border. There we go. So let's try it out now. If I refresh, and if I try searching for A, at, there we go, it filters by email. So that's great, but I do wanna give it a slight modification here because our tables are gonna be reusable and you will be able to search by a lot of different things. So I wanna go ahead and do the following. Instead of hard coding it to email in our reusable data table component. Let's introduce a new data table prop here, which I'm going to call a filter key. And I'm going to make it a type of string. And then I can destructure it here in my props. And then what I'm going to do is I'm very simply going to change this placeholder to be like this, like an object, open template literals and replace emails with the filter key prop, key being capital. And the same thing is true for these two instances of email, filter key. And now we have to go back inside of accounts, page.tsx, and now we have an error here because filter key is missing. So every time we use data table, we are going to decide what the user should filter by when they search. So in my case, this can stay email, right? So now nothing should change. If I search for at A, there we go. This is working. Great. So let's see what is next. We just finished filtering. I want to skip over the visibility. We don't need that. But I do want to get a row selection. So for that, it seems like we are going to need the checkbox and the badge components. Or perhaps we don't need the badge. I think badge came from the one, this one maybe, right? Let's go ahead and let's add checkbox. We'll see if we have some stuff missing here. So I'm going to go ahead and shut down my app and do BunX Chassien, UI latest, add checkbox. There we go. And let's do BunRunDev again. I hope that's going to be enough. So first things first, it says to update columns definitions. So let's do that. Let's go inside of app, dashboard, accounts, columns. And inside of here, let's add the checkbox below our button. So we need to add kind of a before the status, right? So we want to add that here. In place of status, this is where we want to keep our checkboxes. So we have to add it first in this array. so we can copy this entire thing here let's see can we just copy the entire thing? yes, we can just copy the entire thing here but I don't think we need these two, the sorting and the hiding but let's copy for now so just before the status here paste that inside of here and let me expand if you don't want to copy from the documentation you can pause the screen and you can copy from here So what I want us to do is I want us to remove these two. Or maybe we don't need, maybe we can just keep them, right? So let's just copy it exactly as it is. So let's see what that did. So when I refresh now, we should have before status. There we go. The ability to, can we do this? There we go. We can do it. And you can select and unselect everything. Perfect. Now what I want is our data table to be aware of when we select something. So when we select this, I want a little text telling me how many items I've selected. And I also want a button with delete option to appear if I select even a single item. So let's go ahead and do that first. So they going to go ahead and teach us how to do the first thing which is making the data table aware of our selection So let go back inside of components data table now And let's see if we have to add any imports. Looks like we don't. So we only need to add row selection state here. It's highlighted. You're going to see how it looks in a moment. So we're going to add that below column filters here. There we go. Row selection and set row selection. Very simple. and then we're going to go ahead and copy on row select change to be set row selection and we can add that just below get filtered row model so this line right here and then let's go ahead and let's add a row selection as the last item inside of our state so you can see that in here they have column visibility right but we don't need that I didn't find it useful for this project Great. So now the table is aware of our selections. And now we can do this, for example, show selected rows, right? So let's go ahead and let's do this. Let's copy this example. And we can add it, for example, just here at the bottom where we have this div, which will encapsulate these two buttons for next and previous. So we can add that here, for example. So it's going to use the get filtered select row model. So it's going to say two out of five rows selected. Let's see if that is working. So zero out of two rows selected. One out of two, two out of two, one out of two, zero out of two. Perfect. Works just fine. So I believe that are all the options you can actually do for the data table, right? So the next thing they teach you is reusability. So if you want to, you can go ahead and explore that. But we are done with the documentation. And now we have to do some work on our own here. So let's go ahead and let's find our input here. So what I want to do is I want to check if, so open like an if clause here, or what's the equivalent of this? Table.getFilteredSelectedRowModelExecuted.Rows.length. So only if it's larger than zero, meaning at least one item is selected. Only then go ahead and render another button component here, which is going to say delete. And let's go ahead and let's give it a size of small. Let's give it a variant of outline. Let's give it a class name of margin left auto, font normal, and text extra small. and let's go ahead and let's add a trash icon from lucid react here so let me just move that to the top with these there we go let's go ahead and just give the trash icon some styles so class name it's going to be size four and margin right off two there we go and it's going to tell us exactly how many rows we can delete. So open parentheses and then open curly brackets and inside the right table, get filtered rows, select row model dot rows dot length. Like that. There we go. So let's go ahead and try this out. When I select this, there we go. It says delete two. If I select just one, it says delete one. Perfect. So you just adapt that data table on your own without looking at the documentation. So this can give you an idea of any other creative thing you might want to add to your data table. So what I want to do now is I just want to wrap up the props here. So we're going to go ahead and add a couple of more props. So columns, data, filter key. Then we're going to have on delete and we're going to have disabled. And now let's go ahead and add the props for that. So onDelete is going to accept rows, which are a type of row, tData, and an array. And it will return back the void. And you can import a row from tanStackTable. So let me show you. Right here, I've added a row from tanStackReactTable. All right. So we have row and disabled is going to be a Boolean like that. And it's going to be an optional Boolean. There we go. So let's see what are we going to use the onDelete method for. So onDelete for now can be used. Well, let's not use it yet. We're going to come back to this later because there is one slight thing that I want to do first. You're going to see why I want to skip it now. But what we can do is we can already disable this here. So I want this delete button to be disabled if the table itself is marked as disabled. So we can do that, right? And I think that should be enough for now. Perfect. So now if we want to get rid of the errors in our dashboard accounts page, we also have to pass in on delete here. and let's make it an empty arrow method for now. So now if you go ahead and if you select these two items, there we go, it says delete two, but if you go ahead and pass in disabled, then this button is not available. So we're going to use this while we are deleting, while the mutation is pending, right? So the user doesn't fire multiple API requests if there is currently one of the same in progress. So you can remove the disabled for now or you can just mark it as false so you know that you have to change it later. Great, so what we're gonna do in the next chapter is we're going to enable and complete all the functionality we need inside of the data table, which is the confirmation model. That's what I wanna do. And then we're gonna go ahead and create some more endpoints that we need. And finally, editing the accounts. And this is going to be very useful for us. So this was a lot of work. But the good thing about this is that we don't need to write this anymore. So we just created the reusable data table component. And we are going to learn the practices for the accounts page. And we will easily copy and paste that into transactions and into categories. Great, great job. All right. So in order to complete the data table component, I first want to load actual data from my accounts API inside of here. So if you remember, inside of our app folder, let me just zoom in. Inside of app dashboard accounts page.tsx right here, we created a mock data like this. So let's go ahead and slightly modify this. So first of all, I'm going to remove the type here for data, right? Let's go ahead and leave this to be an error, right? It doesn't matter for now. And let's remove the payment import. So I just want you to have import columns from column. And make sure you do this. Make sure you remove that payment type before you continue into doing what we are about to do. So what I want to do now is I want to give my columns inside a proper type. Which type? Well, I specifically want the success format of this API route, the get route. So we have, when I hover over get, there is a possibility of me getting an error and an array of ID and name inside of the data object, right? So I specifically only want this. So I could just copy that and paste it here, right? That would work. But we don't want that. We want to reuse it specifically from this endpoint. And we can do that. So let's remove inside of accounts columns. Let's remove the export type payments and these two comments. And let's go ahead and add the following stuff. So I want us to add infer response type from HONO. And I want us to add client from lib HONO. And now what you can do here is write export type response type. And we're going to use infer response type. And inside of pointy brackets, we're going to write type of client.api.accounts.get. Let me zoom out just so you can see this in one line. But we have an issue here. So this seems fine, right? But when I hover over response type, it tells me that it can either be an error or it can be this. What I want is I just want one element. I just want to get this, the ID and the name in an object. That's the only thing I want. So how can I do that? Well, here are some documentation from the HONO 4.3.0 release. Let me scroll up so you can see. So the release four days ago, as of my recording, 4.3.0 added the exact thing we need. So check your package.json just in case and ensure that you have Hono higher than 4.3, at least 4.3.0, right? So you're definitely going to have that if you're watching this tutorial. So what we can do is we can use the following. We can use status code type. So we can specifically, so this is the exact thing we just made, right? And then we can add a comma and specify the status. And that way we can only get the success message. So it's important that whenever you throw errors inside of your Hono endpoints, make sure that you add a comma and an error code. That way the Hono typing knows, all right, this is only the type if we throw an error. So now inside of my columns here, what I can do is I can add a comma and add 200. And then when I hover, I only get the success format. Let's try it again. When I remove this, then I can have either an error or the data. So that's how it works. So let's add 200 here, but we are not done yet. We now specify the data, more specifically, the first item in the array. That is our type, ID and name. Let's copy that and let's put it here instead. There we go. That is our type. Perfect. Now let go ahead and let remove all of these things which we don need So we can remove this Accessor Key status and header status You can remove that and leave the email simply because we have this cool sorting here already done So we can reuse that, right? So instead of email, this is going to be name because, well, that's the only field we have besides ID. And let's write name here. We don't have to modify this at all. And we're not going to have amount. So let's leave that as it is. Great. Now that we have that, let's go ahead inside of page.tsx right here. And while we can leave this as it is, what we're going to do instead is we are going to remove the mock data. So we no longer need that. Let's go ahead and give this an empty array. There we go. So no errors are being thrown now. But what I want to do now is I want to use my hook, which I believe we already created in feature accounts API, use get accounts. So we have the hook which will call all accounts. So we can use that right here. Let's go ahead and find and write const accounts query to be use get accounts. And let's add the import from features. I'm going to move it along my new account features here. And then inside of here, let's define const accounts to be accounts query dot data or an empty array like that and let's go ahead and let's add accounts inside so now if you have some accounts inside of your database there we go i have to test accounts right here and if i try another account and click create this will automatically get added. Why does it automatically get added? Remember, let's take a look at our use create account hook. So inside of features, accounts, API, use create account. What we do on success is we invalidate the queries accounts, which is this one, use get accounts, which means it will refetch every time you successfully create an account. Great. So we resolved that. Let's head back inside of accounts page.tsx right now. What I want to do is I want to create a loading state. So let's go ahead and do that now. So I'm going to go ahead inside of my terminal and I'm going to add a component, bunx, chat CNUI latest, and I'm going to add skeleton. like that. Great. Bunrun dev again. Then we're going to write here, if accounts query is loading, in that case, let's go ahead and let's just return this dev exactly as it is. So I want it to be the same. I also want to copy the card element. I wanted that to be the same. I also want to copy the card header element. So that is also the same for me here. But we don't have to add any classes here like that. So let's just add card header, card header. Now inside of here, I'm going to go ahead and add a skeleton component from components UI skeleton like that. let's self-close this tag and let's give it a class name h8 and width of 48 like that and then inside of card content here we're gonna add a div with a class name of height 500 pixels full width flex items center and justify center and inside we're gonna add a loader to a lucid react icon so loader2 from lucid react and we're going to give it a class name size6 text slate 300 and animate spin like that so if i go ahead and refresh my page now I think for a slight second, there we go. We have a nice loading animation here. Perfect. So what I want to build now is the ability to delete, or more specifically, to bulk delete my accounts right here. So usually for delete actions, we would use the delete API method. But since this is going to be a bulk delete, we need to pass the body. And in the body, I want to pass an array of IDs to remove. Because of that, I want to use the post request. I actually searched, you know, for some solutions about what other people do here. And most common response was to simply use the post request. so I think it is an actual practice that's done in real world that when we have a bulk delete it might be a better idea to use a post request so let's go inside of accounts right here let's close get and we have post here so let me just see I can indent that like this I will remove the semicolon and I will chain another post inside like this. So inside of this post, it's going to go to slash bulk dash delete. It's going to use clerk middleware and z validator is going to be a bit different. So usually we reuse some schema, but this time we're going to write our own z dot object, which means we need to import zod specifically. So z from zod. Let's go ahead and see. Oh, my apologies. I didn't add it in a string. So it's going to be z dot object. So we are validating the JSON body field. All we need is IDs, which is going to be z dot array of strings like that. Perfect. And then finally, we are going to create a controller here. So let's get to the context. And inside of here, first of all, let's get out using get out C. Then let's get values using C request valid JSON. If there is no out user ID with a question mark, in that case, we're going to return C.json error unauthorized. with a status of 401. Remember, that is very important. And then inside of here, let's go ahead and let's write const data to be await database dot delete accounts dot where. And now we're going to chain multiple filters. So let's add and, which you can import from a drizzle or run. and let's also add in array. So those are the two we need. So and we need both equals accounts user ID to match out user ID. So we are ensuring that the user can only delete their own accounts. And the second in array of what we just passed. So accounts.id need to match values.id, IDs, which we pass right here in the JSON and validate that here like that. And let's go ahead and let's chain dot returning. And let's be specific and only write accounts dot ID. So that's the only thing we care about the return. And let's return C dot JSON here data. There we go. So now that we have that, what we can do is we can create the features, accounts, API, use bulk delete. So let's do that again. Features, accounts, API, and let's create a new hook, use bulk delete.ds. And what we can do to speed things up a bit for us is we can copy use create account. So go ahead and copy the entire thing and paste it in use bulk delete, because both of these are mutations. So first of all, I'm going to go ahead and modify my response type. So I'm going to zoom out just slightly so you can see me write this in one line. So in order to access this, we can't write dot bulk dash delete, right? We need to use the array method of accessing something. So there we go, bulk delete. And then we have to chain another post. So we can't continue here with dot post. It's not going to work. We need to chain it like this post, like that. And we need to copy this and do the same thing here, like that. So make sure you have a request type and make sure you have, sorry, make sure you have a response type. like this, and make sure you have a request type. So request type only has IDs, right? Our response type can be either an error or returning the deleted IDs in the array. Great. Now let's go ahead and modify this method right here. And let's actually rename the query here. So use bulk delete accounts. Now let's change this mutation function, which now accepts IDs as a string to not directly go to the post, but instead it needs to go again to bulk delete. And since you are calling this as a function, you can now chain.post if you want to, or you can use the same method as a bulk post like this. And then inside you would pass in JSON again. There we go. So let me zoom out so you can see. API accounts bulk delete post and you're passing in the JSON exactly as it expects. So now in the on success, the message would be accounts deleted. And the invalidate queries here would, well, still do accounts, but I'm going to add it to do also invalidate summary. So we don't have summary yet, but I don't want to forget this. and later I going to search through my to to ensure that everything is working fine And on error is going to say failed to delete accounts There we go. So we are ready to try this out now. So let's go ahead and let's go back inside of our page.tsx here in dashboard accounts page. and now what I'm going to do is I'm going to add the delete accounts from use bulk delete accounts now I'm going to go ahead and I'm going to move this import alongside these two right here so now we have the delete accounts query and what I want to do is I'm going to add a little const is disabled here to be the following. If accounts query is loading or if delete accounts dot is pending. So one of the two, right? Now let's go ahead and let's find our data table here and let's do the following. Let's change the disabled from hardcoded false to is disabled. and let's go ahead and modify the on delete now here to do the following. It's going to accept row and then we can get IDs using row map r r.original.id and then we can call delete accounts.mutate and we can pass in the IDs like this. But this is not going to work now because we are missing one thing here which is to actually call the onDelete inside of data table. So let's go inside of components data table. So we are not calling the onDelete prop anywhere in our code yet. So how about we change that? Go ahead and find the button where you have the trash icon and the delete text and add an onClick here. OnClick is very simply going to do onDelete, table, get filtered, select row model, execute it, and pass in the rows. And then do table reset row selection. Let's go ahead and try it out now. So I'm going to go ahead and refresh the entire thing. I'm going to select everything, and I'm going to delete it. And there we go. Accounts are deleted. When I refresh, they are nowhere to be seen. If I go to slash API slash accounts, they are completely empty. Great. So if I add first account, it's created. I'm going to add a second account. And I'm going to add a third account. Like that. Let's go ahead and select just this one. And there we go. Accounts deleted. Perfect. So this is great. But since this bulk delete action is quite powerful, it can delete a lot of stuff at once. I want to add a confirmation model. So I want to do that in a way that I can easily reuse it as a hook everywhere. And I found out a method on how to do that. So let's go ahead and develop that. So first of all, I want to give credit where credit is due. I found that on this Medium blog post right here. So thank you, Isaac. I also think that there is another name here worth mentioning, Roy. So thank you both for this great blog post. So I'm going to go ahead and implement what they talked about inside of that blog post here. So what I'm going to do is I'm going to create a reusable common hooks component. And inside of here, I'm going to create use-confirm.tsx. So tsx is important, not just ts, tsx. Let's go ahead and let's import useState from React. Let's import our button from components UI button. Let's import everything we need from components UI dialog, which we don't have. So let's quickly head inside of the terminal here and do BunX ShatCN UI. And let's add the dialog component. There we go. So I'm going to go ahead and import everything we need. We need the dialogue, the dialogue content, the dialogue description, the footer, the header, and the title. So those are the items that we need. And let's export Confuse confirm right here. So these are the props. We're going to have title, which is a string. We're going to have a message, which is a string as well. And what this is going to return is going to be the following. It's going to be a method which returns JSX.element. So that's the first thing. And then another one which returns promise and unknown. That's going to be our function right here. So right now we get an error because we don't return that, but we are going to do that later. So let's go ahead and do the following. Let's go ahead and create a very simple use state here. which is by default going to be null but the type of this state is going to be an object which has resolve which is a method which has a value of boolean and it returns a void or outside of the object it can be null so that's what it's going to be right and inside of const we write promise and set promise as the second argument now let's go ahead and let's write const on confirm My apologies, just confirm. Let's make this an arrow function, which calls new promise. Inside of the promise, it calls resolve and reject as props for the inner method, which very simply is going to call set promise. Open an object and write resolve inside, like this. Then we're going to have handle close, which is very simply going to set promise to no. Kind of like resetting the promise. And inside of here, we're going to have handle confirm. So const handle confirm, which is going to call promise?resolve and pass in the boolean true. And call handle close to reset this hook. And let's have const handle cancel. It's going to work similarly to close, but it's going to explicitly resolve the promise with a false boolean. Like this. Great. And finally, let's create const confirmation. It's important that you call this confirmation dialog method with a capital C because it's going to be used as an element. So immediately return a dialog. It's going to be open if promise is not null. So every time we call handle close, the dialog will close. Inside of the dialog, add dialog content, a dialog header. and the dialogue title. And inside of the title, you can simply render the title, and below the title, add the dialogue description and render the message. And now, still inside of dialogue content, but outside of dialogue header, add the dialogue footer component. Give it a class name of padding-top-of-two. Inside of dialogue footer, add a button component, which we already have imported. and write cancel. Let's go ahead and give this an on click on handle cancel and the variant of outline. Go ahead and copy and paste this button inside of the footer still and this one will just have handle confirm without any specific variant. And it's going to say confirm. And now to fix our TypeScript error, we have to return an array. First, confirm dialog. Second, confirm. There we go. We have a hook which has no TypeScript errors. And let me just zoom out so you can hopefully see this in one line again like that. And now let's go ahead and let's use this here. So I'm going to go back inside of my data table and you're going to see how cool this component is. Well, more specifically, this hook is. So I'm going to go ahead and I'm going to call use confirm from hooks use confirm like this. And now let's go ahead inside of the data table method and at the top here, let's add use confirm. And for the title, we're going to say, are you sure? And for the description, we're going to write, you are about to perform a bulk delete. and now from here let's extract the confirmed dialog you can name this whatever you want right so this is a named constant and the second argument is our confirm method so this is what we're going to do first let's go ahead and first let's render the confirmed dialog anywhere in our code it's important that it's somewhere right in this component and then what we're going to do is We're going to find our delete method here. So we have onClick on delete here. Let's change this to be an asynchronous method. And inside of here, we're going to get const OK to be await confirm. Are you kind of seeing what's going to happen now? So inside of here, if we confirm, we're going to pass in true. And then this constant right here, OK, is going to become true. but if we pass in false it's going to become false so this is a very cool use of promises async and await and now what we can do is very simply if okay only then remove and call on delete let's try it out let's see if we made any mistakes here so make sure you have some accounts here make sure you have the app running refresh let me select everything and click delete and there we go if I click cancel, nothing happens. But if I go ahead and confirm, they are deleted. Amazing. Great, great job. What we are going to do next is we are going to add two more endpoints. And that is so that we can open this test in a same drawer that we are going to reuse in the form. so we need an endpoint to fetch by ID and we also need an endpoint to patch or update the name of the account. Great, great job. So now that we have the ability to bulk delete our accounts, I want to wrap up.\nthe accounts entity by giving us the option to edit them and to also, well, open them exactly like this as we would a new account, but instead I want it pre-filled with the data we have. So let's go ahead inside of our app folder, API and accounts API. Inside of here, we have a get route and two post routes. So just below this first get route, I want to go ahead and I want to chain another get route here. And this one is going to be slash colon ID like this. And inside of here, we're going to add a Z validator to validate the param, which is going to be Z.object. specifically it's going to be a string but it's going to be optional and you're going to see why later don't worry we are still going to validate it here so it's also going to have the clerk middleware and then finally let's get our controller here so just make sure that adding this get route didn't mess up the chaining below so just ensure that this still has no errors Inside of here, we're going to do what we always do. So let's go ahead and get the out using get out C. And let's also the structure ID from C request valid. And we are specifically looking for param. If there is no ID, we can return C.json and we can throw an error here with bad request. And let's write 400. it. Or we can be more specific and write missing ID. And now let's write if there is no out question mark user ID. In that case, we can copy and paste this error, but with a different error message. So this one will say unauthorized and a status code will be 401. And now finally, we can go ahead and we can destructure data immediately from this query. Await database dot select. So we are only going to get account from accounts dot ID and name from accounts dot name. So remember, this accounts variable is the schema in case you forgot about that. And all right, so we chose what we select. Now from where? Well, from the accounts schema. but where specifically, and now we're going to go ahead and use our chaining here first to ensure that what user is trying to fetch is truly theirs by comparing the user ID. And next, we are ensuring that we are only fetching the user account that was passed through the params, which param? The id param right here. So this should take care of that. We don't need returning because this is select, right? So it's automatically doing that. But what we are going to check is if there is no data, right? So if we couldn't get anything in the array, in that case, we can return c json error not found with a 404. And finally, let's return the data. There we go. So now if you hover over your get here, you can see, all right, so it looks like it can only load errors for now. So we're going to see a better response in the hook. Let's go ahead and let's create the hook now that we have a working API. And we can already try out the API. So go ahead and do the following. Make sure that you have like at least one or two accounts created. And go inside of localhost 3000 slash API slash accounts. And now copy the ID and change your URL to slash API slash accounts slash ID. And there we go. So this is that exact account. And if I change it to this ID, then it's test2. If I slightly modify the ID, I get the error not bound. Perfect. Now let's go ahead and turn that into a reusable hook here. So we're going to go ahead and do the following. We're going to go inside of our features, accounts, API, and let's copy use get accounts. And let's rename it to use get account. So individual. Make sure this is accounts and this is just a singular account. Let's rename the hook to use get account as well. And this one will have an optional ID, which is a string. This entire query is only going to be fetched if we actually have the ID. And the query key will be account and an object with ID. And now instead of calling accounts.get, we're going to call accounts, colon ID.get. But we also have to pass in a param, which just happens to be this ID. So this error will say failed to fetch individual account. And this can stay exactly the same. so the reason I made it so that inside of accounts id is optional is simply because it's easier to get the types right right so later you can see when we use this use get account there is simply no way for us to ensure that the the id always exists so we will know because of our code logic we know that almost impossible will it be for it to happen that ID is passed as undefined. And even if it is, this query won't even run because of this enabled property right here. But it's just easier for the types, right? So let's go ahead and leave it like that. And now what I want to do is I want to go ahead and I want to create a Zustand controller here. So instead of features, accounts, hooks, let's create use open account dot TS. So I don't want to call it use edit account because we're going to have that as a name here. So let's call this use open account and we can copy everything from use new account and paste it here. Let's rename this state to open account state. Let's rename the hook to use open account. Let's extend the state to accept an optional ID, which is a string. And let's modify the onOpen to require an ID to be passed if we are opening this drawer, right? So then we have to modify this as well. ID by default will be undefined. And onOpen, we'll have that ID. And besides setting is open to true, it will also assign the ID. And on close, we'll bring back the ID to undefined. Like that. There we go. And now let's go ahead and do the following. Let's go inside of components and let's copy the new account sheet. And let's rename this to edit account. There we go. Make sure you are inside of edit account. And inside of here, we're going to rename this to edit account as well. like that. And let's change this and let's remove use new account completely. And instead, we're going to use use open account like that. So add use open account. I'm going to add the features accounts like this. And then let's see what else we are going to need. So this can stay the same use create account. It doesn't matter. What matters is that we have the ability to fetch our account here. So this is what I'm going to do. I'm going to go ahead here and I'm going to add const account query is going to be use get account. So make sure it's not accounts, just the single account. And let me go ahead and change this to features, accounts and let me move it up with all the other features here. So inside of the account query, we're going to pass in use get account. And remember, we have to pass in the ID and we can now destructure the ID from here as well, because we just changed this to this use open account hook where we defined the ID. And we can now pass that inside of here like that. Perfect. So we now have the account query now. So let's go ahead and do this. Let's define const default values. Let's make that if we have account query dot data, in that case, go ahead and make it name account query dot data dot name. Otherwise, make it name an empty string. And now go ahead and simply pass in the default values here like this. there we go so I think this already might be enough for us to attempt and do something so we need to add this inside of our providers sheet provider here so I'm going to remove this import we don't need it and let's import edit account sheet from features accounts components edit account and simply add it here like that. There we go. And now we have to find a way to open it. So for that, I want to go back inside of the columns of my account page. So let's go Inside of app, inside of dashboard accounts, columns right here. And we now have the checkbox for selection. We have the name field, right? So inside of this columns, let's add the last field here, which is going to have an ID of actions. And cell is going to be row. And it's going to return the actions component, which at the moment does not exist. So let go ahead and do the following Let in order to create it let go inside of the terminal and let shut down the app and let write Banex chat CNUI add latest and let add drop down menu So that's the component that we need for this. So Banex run dev again. Now we're going to be inside of the accounts on the same level as columns and we're going to write actions.esx. Let's mark this as useClient and let's export const actions. And let's return a div saying actions. Let's quickly just give it some props, ID of a string, and let's destructure the same props here. Now let's go back inside of columns and let's add the actions from dot slash actions like that. And let's pass in the ID to be row dot original dot ID. There we go. Let's go ahead and let's check out our localhost 3000. More specifically, the actions page, the accounts page right here. Let's see. There we go. So now we have this third column here, which just simply says actions. We're not going to change that to be a dropdown menu. That's why we've added that. So let's go ahead and import everything we need from components UI dropdown menu here. We're going to need the dropdown menu itself, the dropdown menu content, the item, and you're going to need the drop-down menu trigger like this. Great. And now let's go ahead and change this entire thing to be a fragment. Let's change this to be a drop-down menu. Let's add a drop-down menu trigger. Let's give it a property as child. So it looks like it's child, which is going to be a button component. Make sure you add the import for the button as I did here. And it's going to use an icon more horizontal from Lucid React. So ensure that you have this as well. And this button here is going to have a variant of ghost and a class name. Size 8 and padding of 0. Like this. just below the trigger add a drop down menu content with an align of end inside of here add a drop down menu item which is going to say edit before the paragraph edit we're gonna have the icon edit so I import icon again from the c the react and I'm gonna give the edit a class name of size 4 and margin right of 2. And for the drop down menu item, I'm going to give it a disabled of false and on click. Also, just an empty void for now. Let's go ahead and check this out. There we go. We can now click and we can click edit right here. So let's go ahead and let's go back inside of the actions here. and let's give this more horizontal a class name size 4 just so it doesn't look so enormous. There we go. So now it looks better. And now what I want is that when we click on edit is that we call a hook just like this but our new use open account hook. So let's add import use open account from features account hooks use open account. I'm going to separate the features as always. So inside of here, I'm going to go ahead and I'm going to call this use open account. And this will be on open. We can actually adjust the structure on open, very simply. And inside of here, you're going to call on open and you're going to pass in the ID as the prop. So what's the flow here? On open is going to very simply, let's go ahead and open, use open account. So on open is going to set the ID and is open to true. Is open in return is going to make the edit account sheet component rendered. And then the moment it renders is going to destruct from this ID and it's going to enable this query, which is going to fetch by ID. And then using that, it's going to fill the default values right here. So let's see if that is working. So I'm going to go ahead and refresh the entire thing now. I'm going to press here and click edit. And there we go. So something is opening, but it looks like it's not filling out my data. That's fine. We're going to go ahead back inside of the edit account sheet anyway now. And let's go ahead and continue developing here. So what I want us to do is I want us to destructure the loading, or we can just do this. So how about we just write const is loading to be account query is loading. Let's just keep it simple. And then inside of here, we are conditionally going to render the account form. So if we are loading, we're going to render our usual loader like this. So this is going to be a div with a class name of absolute inset0 flex item center and justify center. And it's going to use a loader2 icon from Lucid React. with a size 4 text muted foreground and animate spin. Make sure to import loader to from Lucid React. So I'm just going to add that to the top here in the edit account sheet component. And let's add the alternative here, which will be our account form. So let's see if this conditional rendering automatically enables the default values or if we have a bug somewhere else. So if I click edit, there we go. It says test. If I click here, it says test to. Perfect. So what we have to modify now is that this account form now accepts ID as well. And now what I have to do is I'm noticing a couple of bugs here. So first of all, it's showing it changed this save changes and it changed delete account. but our title and description are still showing a new account instead of edit. So let's go ahead and change that here. And that's actually right here. So edit account. And in the description here, let's pass in edit an existing account. There we go. So now it makes more sense. When we click add new, it's completely blank. But when I click on some of these, it opens up this. Perfect. So now I want to go ahead and I want to create a patch function. So before we can build the patch route, we first have to visit app API route route.ts. And remember, inside of here, we only added two handlers for get and for post. So let's go ahead and enable it for patch now as well. Because that's the one we're going to need. If you don't add this, you're going to get 405 methods. So let's get inside of accounts now. Whoops, I opened it twice. And let's go ahead and do the following. So at the bottom here, I'm going to change patch. And it's going to do the same thing. It's going to go to slash ID. Let's go ahead and collapse this. So ID, we're going to have the clerk middleware and a Z validator here, which is going to validate the param, which is going to be an object. id z is going to be a optional string like this and now let's go ahead and let's pass in our asynchronous controller here so first things first let's get the out get out context then let's destructure the id to be c request valid param but we also need the json object here so remember we can chain validators as well z validator in this one we're going to check for json and what we are going to check is insert account schema dot pick name true so the same thing that we actually did in here z validator json insert account schema pick name but in this case we are chaining two validators first to validate the id we are trying to patch and the second one, the actual JSON object. Great. So now what we can do is we can also get the values from C request valid JSON like that. If there is no ID, let's return C dot JSON error missing ID with a 400. If there is no out user ID with a question mark between them, return c.json error unauthorized with a status of 401. And then finally, let's find and edit our account. So const, destructure the single item, await database.updateaccounts.setvalues where, let's use and to ensure that this is this user's account and let's also ensure that it's the account that was passed in as the ID and let's add .returning as well. there we go if there is no data returned that means that we could not found whoops not found we could not have found that account otherwise we are returning the data itself there we go so now we have a working edit request So what we have to do is we have to create use edit account And we can thankfully copy some stuff here. So features, API. We can copy the same thing as create account. Just rename it use edit account like this. And let me go ahead and expand this as much as I can. So we are changing to not target just the accounts, but we are targeting, well, not even post, right? We are targeting accounts, ID, and then patch. So this is how it looks in one line, right? So let me copy this and replace this. And the JSON is still the same, right? We are accepting only the name. Let's rename this to use edit account like this. And let's change the JSON. So there is no error here because the JSON is the same for creating the accounts and for our ID patch JSON. So it's the same thing, right? Let's see. Is it the same thing or did I make them? Oh, well, it's not the same thing because it accepts JSON. Yes, that's true. JSON is correct, but it also needs to accept a param ID. Where do we get the ID from? Well, from useEditAccount, we will extend it by adding an optional ID here as well. There we go. So ensure that you are passing the param ID in the JSON of the patch function here. Instead of account created, it's going to be account updated. We are going to update accounts, but we are primarily going to update account with that ID as the key. I'm going to add to do invalidate summary and transactions later in the future. And this will be failed to edit account. Now that we have this hook, we can head back inside of our edit account sheet component. So let me go ahead and add this import use edit account. I'm going to change this to be features accounts like this. And then we're going to go ahead and add some more mutations here. So I'm going to add const edit mutation to be use edit account. And I'm going to pass in the ID as well. and then in here I'm going to go ahead and create const is pending to be edit mutation dot pending is pending for now it's only going to be this so that's why I added a space because later we're going to have another one here so is pending here and let's change the on submit here so we are no longer going to have this use create account we can delete the mutation and use create account. And we can remove the import, use create account. No need for that. So that's going to break here. But now we have the edit mutation. So let's add the edit mutation with the values. That's going to work just fine. Great. And I think that this shouldn't... All right. So instead of here, is pending, is no longer going to be controlled directly by mutation is pending. But we're going to use that constant is pending, which for now holds the exact same thing but later is going to be extended with another mutation which is going to be the delete mutation. Let's try this out. My account is named test. I'm going to click edit and I'm going to call this edited. Let's click save changes and there we go. It is edited. Let's try again. Perfect. Let's refresh to ensure that data is preserved and it is. Perfect. I can now save this right here. Great. What I want to do now is I want to create a separate delete endpoint, right? So we have the bulk delete. Let's go ahead and let's quickly create the individual delete endpoint. So the same way we needed to enable patch requests, we also need to do that for delete requests. So let's copy and paste this and add delete. There we go, inside of app, API, route, route.vs. To speed things up, we can copy the entire patch ID method because it's going to be very similar. So I'm going to copy the entire thing and I'm going to chain it together like this. So I have twice patch and I'm going to change this to delete now. And I'm going to remove the validator for JSON because it's not needed. And no, I no longer need the values, right? But these rules can stay the same. If there is no ID, I don't know what to delete. If there is no user, you are unauthorized to do this. And now instead of update, it's going to be delete accounts. And there's not going to be the set chain here. The where rule will stay the same. And for returning, we can just say which ID we remove. No need for the entire data. And here is the same rule, not found, 404. That is it. That is our delete route. So just make sure you've chained it with .delete. Make sure that your existing patch, which you've copied, is intact, so you didn't accidentally cut it. And make sure you've removed the validator for JSON. We don't need it for the delete requests. And of course, ensure you've enabled the delete requests. So now what we can do is we can go ahead and copy the useEditAccount, features, accounts, API, use edit account, copy and paste it and change it to use delete account. Rename this to use delete account. And all we're going to have to change is these patch methods here. So instead of patch, these two are going to be delete. And in fact, we're not even going to have the request type for this one. So you can remove the request type entirely and in place of it you're simply going to remove it and remove the trailing comma as well. Go ahead and replace patch with delete in the mutation function as well and simply rely on the param and no need to add JSON in the props here either. And we're going to write account deleted here. Failed to delete account. we are going to update account and the id the accounts we are going to update the summary and transactions as well later in the future when we have those things great let's go ahead and let's do the same thing now inside of edit account go ahead and copy and paste this use delete account from use delete account like this. So just make sure you've added that. Go ahead and add it here. So edit mutation comes delete mutation from use delete account. Pass in the ID. And now we can chain these two together. So either edit mutation or delete mutation is pending. We are going to disable the form, right? So the user cannot spam with API requests. There we go. And let's go ahead now and let's attach that right here. So we need to pass in the onDelete method, which is optional so far. So in here, it can be the deleteMutationMutate. Like this, I believe. We are not passing anything inside, like that. Let's just ensure inside of the account form that we are actually using on the delete. So we are using it. It's optional. It's right here. And it should be used, let's see, right here in handle delete. We have a safety check here. And we are using it inside of this button, which is only enabled if we pass in the ID. So make sure you've passed in the ID. So if we try this now, for example, I'm going to open the test too. And if I click delete account, there we go. It says account deleted. Perfect. And it's actually deleted. But there are a couple of things we could improve here. First of all, well, it could close the drawer, right? But it could also go ahead and do the confirmation for us. So let's go ahead and do that. I'm going to go ahead. Now I'm going to import our very cool use confirm hook from hooks use confirm. and then I'm going to go ahead and define it here. Use confirm. For the title, we're going to say, are you sure as well as our previous one and we're going to write, you are about to delete this transaction. Let's extract the confirm dialog and the confirm method. Let's make sure that we are rendering the confirm dialog somewhere. So what we can do is we can wrap the entire sheet component in a fragment, invent it, and simply add the confirm dialog here. It is a self-closing tag. And then let's define our onDelete method here. Let's make sure it is an asynchronous method. Const OK is going to be await confirm so we know whether the user pressed yes or no and if we are okay we are going to call delete mutation dot mutate but for the variables we're going to pass undefined because there is there are none and then we're going to open the options object and write on set on success and in here on close because that's what we wanted to do right and then let's go ahead and use this on delete right here instead there we go so now we should have a much better experience with deleting accounts so if i go into edit and if i click delete are you sure you are about to Delete this transaction. Cancel. Does nothing. Confirm. Closes it and deletes it. Perfect. So one more thing I want to do is add a shorthand delete here alongside edit. So for that, we go back inside of our columns. Inside of app accounts columns right here. Inside of actions more specifically. So let go ahead and let I believe we have to import the handle We have to define const handle delete right It is an asynchronous method And we're going to do the following. Let's add const delete mutation to be use delete account and pass in the ID. So make sure you've imported this from the features like this alongside use open account. and let's also add our use confirm from shared hooks. So we have the delete mutation here, but we also need to add our const use confirm. Are you sure? And description, you are about to delete this transaction. let's get the confirm dialog and the confirm method let's render the confirm dialog right here so we already prepared the fragment and we're going to call const ok await confirm if ok delete mutation dot mutate like that. And let's go ahead and use handle delete now. We can copy and paste the drop down menu item. And this one will simply call the handle delete. It's going to be disabled if the delete mutation is pending. And the edit is also going to be closed if delete mutation is pending. and instead of edit, this will say delete, and we're going to use the trash icon from Lucid React like this. There we go, and I have a dot here, but I don't have it in my edit account sheet, so let me just add the dot here. All right, now I am at peace. Let's try it out now, so this should be it for our accounts entity, I believe. Delete, cancel does nothing. Delete, confirm. There we go. Perfect. So we just enabled a bunch of things for our accounts. We can edit them, we can load them, we can individually delete them, but we can also bulk delete them. Amazing. Great, great job. you're going to see now how easier it's going to be for us to create the categories entity, which is practically going to be the same thing as accounts variables wise and size wise, right? Obviously, it's going to serve a different purpose. But for code, it's going to be almost exactly the same. So you're going to see how much these components that we've created and these methods that we've established are going to speed up this process. So it's going to take, I want to say, three times less than it took us to complete the accounts page. Nevertheless, great, great job. So now that we've wrapped up our accounts entity and we can create new accounts, we can individually delete a single account, we can go ahead and bulk delete accounts, and of course, we can always edit a specific account. let's see how fast we can build the equivalent categories page because it's going to be very, very simple. And we have a lot of components finished now. So first of all, we start in the schema. So let's go inside of database schema.ts. And we can copy and paste the existing accounts. But I'm just going to do it below the insert accounts schema. And I'm going to call this categories. And let's of course ensure that our PG table is also named categories. It's also going to have an ID, it's going to have a played ID, it's going to have a name and a user ID. So exact same fields. And if you're wondering what does played ID do in both of these, well, we're going to come to that later when we connect to an external service, which will be able to read from our bank accounts. So for now, we are not using that, but later we will. So we are already preparing for that. Great. So we have that now. Let's go inside of our terminal. Let's shut down the app and let's run bun run database generate our script to generate SQL migrations. And there we go. We now have a migration script here. So let's go ahead and do bun run database migrate. And now this will push it to NeonDB. Let's go ahead and visit our database studio. And let's just confirm that everything is fine. So inside of here, I should now have categories and I do and they have ID, blade ID, name and user ID. Perfect. So I'm going to go ahead and do bun run dev on my app now. And let's go ahead and let's start. So what we can do now is we can easily copy and paste our route, right? So inside of app API, inside of route folder, we have accounts. Let's copy and paste that and let's rename this to be categories like this. And let's go ahead inside of route.ds and let's import categories from dot slash categories like that. And now we're going to go ahead and we're going to chain slash categories right here and make sure they use the categories model. Not model, but route. Right. There we go. So now if we go ahead and go inside of localhost 3000 slash API slash categories, we are going to load accounts, right? Because we just copied and pasted the route for accounts. So regardless, because we mapped this under categories, inside of the code, we are still working with accounts and insert account schema. So now let's go one by one and let's modify this so it works properly. So I'm going to go ahead and do the easy thing. I'm going to remove the accounts import because that's going to give us errors everywhere where we need to replace them with categories, right? This is kind of an easy way of us of doing this. So let's import categories instead. And the same thing with insert account schema. Well, we are missing the insert categories schema, right? So let's go back inside of our schema file. Let's copy this and let's paste it here. And this is going to use the categories and it's going to be called insert category schema. Like that. Perfect. So let's go back inside of categories and let's import insert category schema. There we go. So we have two unused imports and a bunch of errors we've got to fix. Let's start with the first get route. So inside of here, what we are going to do is we are going to select the same thing, ID and name from categories, from the categories table. And we are going to ensure that it's only the person that created this category that can see these categories, right? Because users can have personal categories. We don't want to share them globally with other users. Now let's go ahead and go to the other route. So right now I think, I'm not sure if this is going to work now because we have errors. But if I refresh localhost, let's see, there we go. So we still have errors. We can't try this out yet. Okay. But we know that our get route has no errors. But this get does have errors, I believe. There we go. So let's go inside of the get individual ID. so the validator for the baram stays the same, right? The error codes are the same, but inside of here, we are no longer working with accounts, but instead we are working with categories like this. There we go. So we resolved our get ID route. Now let's get for the creating the route. So this time we are using insert category schema right here. And I believe the rule can stay the same. We are only using the name. There we go. And we are not inserting into accounts, but instead into categories. And we are still using the same create ID method here. There we go. That's our create request. Now for the bulk delete, same thing. Everything is exactly the same. But instead of removing accounts and instead of doing filtering with accounts, we are all changing that to categories. There we go. We just saved a bunch of time from writing this together. from writing this again. So in the patch request, we do the same thing. Instead of insert account schema, it is insert category schema like this. And below here, we are using categories like that. And that resolves our patch request for the categories. And I think this is the last one, the delete by ID. So everything above stays the same, but we are manipulating the categories, not the account. And there we go. All of a sudden, no errors inside of my application. So I recommend that you expand all of this and just take a peek that nothing is red and there are no errors. There we go. This is fine. And you can also do a search of accounts just to confirm that you by accident don't use account or accounts anywhere in this code because there shouldn't be any. So right now, as you can see, my data here is completely empty because it's actually loading the categories. Perfect. So now let's go ahead back inside of localhost 3000. And what we have to do now is we have to create the page for categories. So we can also copy and paste our existing accounts page. So let's copy and paste that and let's rename this categories. so just by doing that nothing should change except that when I click on categories here I will no longer get a 404 instead it's going to load the accounts page because we just copied and pasted it again if you're still getting a 404 here make sure that your navigation component has the proper href to go to slash categories because that is the name of the folder we just created categories. Great. Let's go inside of categories page.vsx and let's change the simple things first. So instead of it being accounts page, it's going to be categories page like that. That's the first change. Then what I want to do is I want to change the title instead of accounts page, it's categories. There we go. So it's already looking better. What we have to do now is we have to create all of these equivalent hooks, but to manipulate, well, categories, right? So let's go ahead and do that. Let's see if we can do it just by slight modification of the existing ones. So let's go inside of features and let's copy the entire accounts and paste it here. And let's rename it categories. Now, first things first, let's go ahead\nLet's resolve our hooks here. So we're going to have use new account renamed to use new category. Let's go inside of that. Instead of new account state, this is going to be new category. New category without a typo. And this can stay exactly as it is. Then let's rename this to use open category. Let's go inside and let's change this from open account to open category. Again, a typo on my side. And the logic can stay exactly the same. Great. We have the cook hooks resolved. Now let's go inside of the API. Let's start with a simple use get accounts. And let's rename it the use get categories. Just ensure that you're doing these changes in the categories folder. Don't accidentally rename your existing accounts, right? So we copied and pasted the accounts and renamed it into categories. So just make sure you're modifying that. So we just renamed this to use get categories. And now let's change this to use get categories as well. Let's go ahead and modify the query key to be categories. And now we have to change the response to not call accounts, but instead call categories and failed to fetch categories like that. There we go. Use get categories is now working. Let's go inside of the individual use get account and rename it to use get category right here. let's rename it to use get category the id stays the same the query key is category individual and an id and the api is not accounts but categories and targeting a single id and since we copied and paste the entire api everything is the same the types are exactly the same let's go ahead and resolve the error so account should be failed to fetch category like this. There we go. Now that we have that, let's go ahead and let's do use edit category like this. And inside of here, what we are going to do is, again, we're going to replace instances of accounts with categories because everything else stays exactly the same. The patch request, the JSON, everything is the same. And let's change the categories here as well. categories like that. And the types are the same. We accept the ID param and the JSON. Instead of account updated, it's going to be category updated. And we are going to refresh the individual category. And we are going to refresh the categories key like that. And failed to edit category as the error. And this to do can stay the same because later we're going to have refresh that as well. Now let's go ahead and let's modify the use delete account to be use delete category. And let me just close everything besides this. All right, so use delete category. Let's rename this to use delete category. Let's go ahead and replace the account instances with categories. Same thing in the mutation function. We are working with the individual category. category deleted, and categories, and failed to delete an individual category here. There we go. Now let's go ahead and let's modify use create to be use create category. Use create category. Change the accounts instances to categories. And same thing for the mutation function. Everything else is exactly the same because they're so similar. So category has been created and we are refreshing the categories here. And let's write fail to create category like this. There we go. And last one, use bulk delete. Well, use bulk delete can actually stay the same. Let's be specific and let's call it use bulk delete categories. So I'm going to go ahead and find the previous one in the accounts. And I'm going to add it as well because I want to be explicit about the naming. So this is the full name. Use bulk delete categories. There we go. So use bulk delete categories instead of accounts. And again, instead of accounts, it is categories. Using the bulk delete method. Inside of here as well. So categories. categories deleted, and we are refreshing the categories query key and failed to delete categories. And I believe that is it. So we have everything we need for the API. Now let's go ahead and let's modify our components here, which again are going to be very, very similar. Let's go inside of the account form and let's rename it to category form. Inside of category form, let's go ahead and do the easy thing. And that is to remove the insert account schema and instead import insert category schema. So we have the errors telling us exactly what we have to change. There we go. Now this can stay the same. Form values are the same. The props are exactly the same. And let's rename this to be category form. The form stays the same. Handle submit, handle delete. And now we're just going to have to slightly modify the placeholder because the name is still the only field that exists when creating a category. So this now instead of cash, bank and credit card can be for example, food, travel, etc. And let's go ahead and modify the create account to be create category. And below that, delete category. There we go. Now let's go ahead and let's go inside of the new account sheet and let's rename it to new category like that again let's do the easy thing and that is well first of all let's do let's do this let's remove the schema and let's write insert category schema so we get this easy error here all right so only this place and now what we have to do let's leave these three imports for last Let's go ahead and modify the labels first. So new category sheet like that. Let's leave this as it is. And let's write new category like that. And let's change the description. Create a new category to organize your transactions like this. We can leave this as it is. And now we can start replacing our components and hooks. So first of all, I want to go ahead and I want to remove the account form. Instead, I'm going to import category form from dot slash category form or features, categories, components, category form like that. Let's just see if there are any errors. There aren't. So this should be in one line like this. So now we have category form and we have this error. If I replace it, there should be no errors at all. There we go. It matches perfectly. Now let's remove the use new account. And instead, let's import use new category from hooks use new category. But I'm going to change that to features categories like this. There we go. Let's find the error. Instead of use new account, it's use new category and nothing else changes. And let's do the last thing for use create account. Instead, let's import use create category from API, which I am again going to change to use the add features sign. You can leave it like this if you want to. I just like to be consistent with my imports. Let's find the error. And there we go. Use create category. That's it. That is our new category sheet. Now let's go ahead and do the last one, which is edit account sheet. And let's rename it to edit category. Let's go inside of here and let's do the same thing. So easy things first. How about remove the insert account schema in favor of insert category schema. and let's replace that with the error here. Now let's modify the edit account sheet to be edit category like this. And now we're gonna go ahead and modify a couple of things. So first of all, let's find some text here. So edit category, edit an existing category. Let's see if there are any other instances. There are not. But I think that in this use confirm, I kept writing delete the transaction when it's actually delete account, right? So let's go ahead and fix it here first. You're about to delete this category, not the transaction. It looks like I made a typo. Great. And now we're going to go ahead and delete things one by one. So first of all, it's the account form. We are no longer using that. So we can remove that. And instead, we can import our category form from dot slash category form or from features, categories components category form. Let's copy the category form and instead let's use it in place of the account form here and nothing should change because it's exactly the same. Let's remove the use get account in favor of use get category individual category. From features categories like this. Let's find where we have the error and there it is. While we are here, let's also modify to not use account query, but instead category query like this. And then let's change is loading to use category query is loading. And below here, let's modify category query data, category query data name. And that is it. Great. Now let's go ahead and remove the use edit account, sorry, use open account in favor of use open category. And I'm just going to copy this and replace that here. Let's find where we use use open account. And this time it's use open category and nothing else requires a change. Let's remove the use edit account now in favor of use edit category. Do I have use edit category? Perhaps I don't. Let's go ahead and see if I made a mistake here. Use edit category. I did not rename it. So inside of my features folder, inside of categories, I do have use edit category, but I forgot to rename it. So use edit category like that Let try this again Use edit category There we go We can now safely import that as well So we are doing this inside of edit category sheet right Let see if there are still features and this should be slash categories like this. Let's see where we have to use that. So it's here and edit mutation logic can stay the same. And let's do the last one, which is use delete account. We no longer have that. Instead, we're going to have use delete category. Let's go ahead and write features, categories, API, use delete category, like that. Let's find the error and let's use it here. There we go. I believe we handled all the logic. So to make sure, this is what I want to do. I want to go inside of features here. I want to go inside of categories. And what I'm going to do is right click and I'm going to write find in folder and I'm going to search for account. There we go. So I have no instances of account and I have no instances of accounts. But if I search for category, I do. Categories, I do. Perfect. So just make sure that you have no instance of account or accounts in your categories. That way you know that you properly replaced and renamed everything. Great. So we now have a fully working features here. What we have to do now is we have to go inside of the providers, the sheet provider, and go ahead and copy and paste this to add a little space. So you separate features. And these two next ones are going to be from features categories. And it's going to be new category. Okay, maybe it's easier for us to do this, right? import new category like that and below that import edit category from features categories components great and now we're going to go ahead and write new category and below that edit category there we go perfect and now that we have that let's go inside of the app dashboard let's go inside of categories and let's go inside of page.dsx and let's slowly resolve this as well so we change the labels, right? So that's fine. Now let's go ahead and change the individual hooks here. So we're not going to use use new account. Instead, we're going to use use new category from features, categories, hooks. Let's find the error and let's replace it. Let's also replace the name of new account and everywhere where we use it to new category. Like this, a new category dot open here and I renamed this to new category. And that's it. Let's go ahead and next use bulk delete accounts. We remove that in favor of use bulk delete categories. And use delete categories here. Let's fix the error here. And let's fix the mutation here. There we go. Everything matches exactly. And let's remove use get accounts in favor of use get categories. So multiple, make sure it's multiple of them. Let's replace the use get categories. And let's change this to not be accounts query, but categories query. And in here, we're going to use the categories query data. And these are now going to be categories. Is disabled is going to look for the categories query instead of the accounts query. And same thing for this if clause here. and then copy and paste this categories constant. And we're going to paste it here. There we go. And, oh, I left the filter key to be email. All right, so this should be changed the name both here and also in the accounts page. I forgot about that. We're going to go back and fix that as well. Great, so this now actually works fine, but we do have to resolve a couple of things. So here in the columns, right here, in the categories folder, right, we have to change the response type to not use accounts, but categories, even though they are exactly the same. So nothing visible is changing. But if you ever add some new fields, you obviously want to read them here, right? Now let's go inside of the actions. And in here, we have to modify, I think, the last of our hooks. So let's remove use open account in favor of use open category. And let's go ahead and see where we have to use it. So right here, use open category. Perfect. Let's change this label while we are here. So you're about to delete this category. And let's go and remove useDeleteAccount in favor of useDeleteCategory. Like this. There we go. And let's add that here. And the deleteMutationName is generic so it can stay the same. And I think that we don't have to modify anything else. So this deleteMutation seems to have some error. But I think there's just some weird cache, if I'm not mistaken. If I go ahead and reload my window, I think it will go away. There we go. It went away. Perfect. So if I'm not mistaken, we should now be able to fully control our categories separately from the accounts. So let's just ensure that we have a couple of accounts. I'm going to call this account one just in case. There we go. So ensure that your accounts are still working intact. Now let's go ahead. And first of all, great. The categories are empty, meaning that we are loading the categories. Let's right here. There we go. New category. I'm going to call this food. Let's see. And there we go. Perfect. Let's go ahead and edit this to food too. This works. Let's check out the accounts. Great. Nothing is transferred in the accounts, meaning that this is fully on its own. Let's go and try and delete this individually. There we go. Let's try creating a food category, a travel category. Let's select both of those and let's delete them. Let's confirm. There we go. Perfectly working. Now let's go ahead and let's modify just a couple of changes we noticed in the accounts, right? So I want to go ahead and go inside of my, is it features, accounts? I think inside of here, I'm going to go ahead and right click and I'm going to do find in folder and I'm going to search for transaction. uh all right so this to do is fine to do is fine but we have the edit account sheet component right here so let me show you where it is it is inside of features accounts components edit account let's change this confirm message to be you're about to delete this account right because that's what we are deleting all right is that the only place let me go ahead and find in folder again transaction that is fine so it was only that one instance that was wrong and there is also another thing that i believe we noticed here and that's that we called this use bulk delete when it should be use bulk delete accounts like this and now this will open up another page where we have to use that and that is our app folder dashboard accounts page.tsx so right here we are this just changed automatically. So usually we had just use bulk delete, but we renamed that to use bulk delete accounts. So now it is use bulk delete accounts and nothing else should require the change because inside we had it properly named. It was only the file name, which was missing some information. And I believe that we also have to go inside of accounts page.tsx and change our data table to not filter by email, but by name. There we go. So if I go ahead inside of accounts now, let's go ahead and confirm that I can search for account one. Great. And let's confirm that our bulk delete is still working. And it is accounts deleted. Perfect. And let's see if we have a working message here. You're about to delete this transaction. So we still have this issue. that is because of our accounts columns or actions. It's because of accounts actions.psx. So you are about to delete this account. That's the proper message. There we go. You are about to delete this account and that is perfectly fine. That is the correct message. And if I try it inside of here, you are about to delete this account as well. Perfect. So that's it. It took us, I don't know how much faster to create categories than it took us to create our accounts. And that's because we established so many concepts and so many reusable components that we can easily do that, right? And it helps that the schema is almost identical. Well, not almost. It is identical to the accounts page. So the next one we're going to do is the transactions, which are going to be fairly more complicated than the previous two, especially because they're going to have relations with the two. So we are going to have to complicate it a bit. And that's why I first wanted to create the accounts and the categories so that now we can fully create transactions without going back to them. Great, great job. So now that we've wrapped up the categories schema and page and API, let's go ahead and let's finally create the transactions. So head back inside of your schema file. So database folder schema. And let's export const transactions. We're going to use pgtable again. And make sure you have a matching name inside. The ID will be exactly the same. So we can copy that like this. And our amount is going to be interesting. So it's not going to be a decimal. It's not going to be a float. It's not going to be the money currency. It's just going to be an integer. So we have to add that from pgcore right here. Make sure you import integer. and give it the same name amount and make sure it's required by adding not null. So why did I choose integer for storing the currency in our database? Well, let's look at all the ways that we can store currency in our database here. So the first idea that comes to mind is using floats or doubles, but we know that this is a no-go because they do not offer precision. It might look good and easy to store it that way, but the moment you start calculating, you're going to see that they're not very precise and this will turn into a bunch of errors. The second option we have is using the decimal or numeric fields which come in Postgres, for example. That is fine, but the problem is that numeric or decimal does not exist in JavaScript, so it has to be parsed as a string, which will create some problems with our end-to-end type safety, especially when it comes to forms. It will also require us to use additional libraries to handle this, like decimal.js. And it's also not cross-language compatible. If we change the database or if we change the language it will require us to change the complete additional library stack and also to find a way to support that in our database So I settled on the third option, which is using the integer of the smallest unit of the currency, which would mean storing dollars as cents in our database, which basically means storing it as an integer. right? But we're going to go ahead even further and we're going to store it as milli units. So milli units means that we're going to multiply our value by thousand. And this way we can support up to three decimals because later when we integrate played, which will read from our bank account, they have a documentation about their amount property, which says that it might return three decimals at one point, especially if we are working with crypto values. So because of that, I made a decision to go with milli units. Means that when storing into the database, we are going to multiply the amount by a thousand and we are going to store that integer inside of our database. And then when we bring it back to the client, the client has to know that and then the client will divide it by the thousand to display the number to the user. So what we ensure here is that we work with types that exist inside of JavaScript ecosystem. And we also make it cross language compatible. So if we ever change the database, we don't have to worry about whether the new database has the decimal field, right? Because all of them have the integer. And the same thing is for, you know, the programming language, they all work with integers in pretty much the same way. So I think that's the right choice for this application, you can, of course, you know, discuss and try your own solution. But I recommend that you follow exactly as I'm doing, especially because it's going to get complicated with all the calculations and stuff. So we have the amount, we're going to have the payee option, which is a very simple text, which says payee. So I didn't make payee any kind of separate entity because payee can change from ATM to ATM from cash register to cash register, So it can change depending on the location and everything. It just varies too much. So it's safest thing to do is to just make it a required string. And then we're going to have some notes, which are going to be some personal notes, which user can write. And they're, of course, optional. Then we're going to have a date, which will be a timestamp option, not time, timestamp from Postgres core. So make sure you've added the integer, the pg table, and the timestamp here. Let's further define this by giving this the date field, giving it a mode of date, and let's make it required not null. We are not going to give it any default values because this is something user will enter themselves, right? They can add transactions which will happen in the future or ones which have happened in the past. So we don't want to pre-fill this for them, right? And now what I want to do is I want to create relations with transactions and categories and with accounts. So first, let's go ahead and let's do this. So right now you can see that this transaction doesn't belong to anyone, right? There is no user ID here. But we need to find a way for this transaction to only be loaded for that user. We can do that by creating a relation with the account. So this is what we're going to do. We're going to write account ID and we're going to write text account underscore ID. So why text? Well, that's because we use text for our IDs. So our account has text as the type of ID. So this is correct. So we're going to have account underscore ID and it's going to reference to open a function here accounts, which we already have dot ID. So that's what it will reference. Right. And now we're going to add some additional rules here. For example, what happens if we delete the account? Well, if we delete the account, I want to remove all the transactions that that account had. So we can do that by defining an additional option here on delete cascade like that. If you don't want that, you can set that to be null or something else. But here's the problem. This is required for us, right? So we cannot do anything besides remove this because account ID field will be required. We cannot do set null here because we are doing not null here. So we are not getting an error, but we will get an error later when this happens. So make sure this is on cascade. And we will also create a category ID here. So again, text category underscore ID. and this time we are only going to add a very simple on delete. My apologies, I didn't add the references at all. So dot references here is a function which goes to categories.id and on delete here, we'll simply set it to null, meaning it's uncategorized. So if we delete the category that this transaction is attached to, No point in removing the transaction, right? We can just set it as uncategorized. So that's why this will not have the not null because it's optional, right? If we really want to, we can leave the transaction as uncategorized. But we are not done with defining our relations here, right? So we just have the references, but we haven't defined proper relations like one to one, one to many, right? So here is the documentation where you can find it. just in case you're wondering where I got it from. So inside of drizzle documentation, access your data, we have query. And in here we have declaring relations right here. So we're going to use this relations import. And inside of here, we can define either a one to one relation or one to many relation or many to many relations. So in our case, we're going to work with one to many or like many to one. So let's go ahead and do the following. First, let's resolve the accounts. So we have to import from Drizzle ORM itself relations. So let's go ahead and let's add relations like that. And now in here, I'm going to add export const accounts relations like that. That's going to be relations field, which works on the accounts schema. And inside of here, we are going to destructure many. and we are going to return an immediate object down here. And what relation is we going to have? A relation with transactions. So one account can have many transactions like that. Perfect. So that's it for our accounts. Now we have to create the same thing for the categories. So we can copy this. Let's go ahead and find this. And we're going to rename this into categories relations. it's going to work with categories so very important for you to change this and again so a category can have many transactions inside great and now we have to create the relations for transactions so let's go ahead and export const transactions relations here so that's going to be relations transactions and inside of here we're going to destructure one and return an immediate object here. So each transaction can only have one account. So accounts like that. And now let's define the fields. So we are working with transactions dot account ID is going to reference accounts dot ID. So that's how we create a one to many relation here, right? We define the many there. And in here, we defined that this field account ID references inside of the accounts, this ID right here. Great. So we have that. And now we can copy and paste this and rename this into categories. So categories work with categories, categories.id, and this will be category ID, just like this. There we go. And now let's go ahead and let's export const insert transaction schema to be create insert schema, which we already have, but this time four transactions. And we're going to slightly modify it because date is a type of date, but we can't exactly work with that in JavaScript. So we are going to add, we need to import zod first. We have to slightly modify it so it doesn't create errors in our forms because we're going to reuse it all the way there. So we're going to write z.coerse.date. There we go. So this way, we won't have any TypeScript errors with dates. And there we go. You can see the date is now a type of Zod date, which is compatible with our hook form. Great. Perfect. So we have that. And now it's ready to, well, generate that SQL and migrate it. So inside of our drizzle, last thing we did is we added categories. So let's go ahead inside of our terminal here. Let me just see what this error is. All right. So this is some TS config here. I think it will go away if I just reload my window. All right. Let's get inside of the terminal. Let's shut down the app. And let's do one run database generate. And there we go. Three table accounts, categories and transactions. Let's see what happened now. So inside of our new SQL file right here, there we go. So we created the table transactions and we added foreign keys or relations to that transactions. And let's go ahead and go inside of the terminal now and do bun run database migrate. Let's see if we are going to have any errors. No errors at all. Perfect. In case you ever get errors with the bun migrate, you don't have to worry. You can always just delete your drizzle folder and that will restart the entire migration process. And if you still have errors, then I recommend that you create a new database in NeonDB and connect again. All right. And now let's do bun run database studio just to check it out. So I'm going to go ahead and open this. And now we should see the transactions and we should also see the relation. So if I go here, there we go, we have account ID and category ID, and these are our relations right here. So inside of my accounts, I also now have the transactions relation here. Same thing here. So these are going to appear as actual relations which you will be able to click and the studio will redirect you to that Perfect Great And now I want to go ahead and I want to build my API endpoint So we're going to leave the front end for another chapter because the front end is going to be, well, slightly more complicated. So let's go ahead and let's do the easy thing, which is to copy our categories, right? So let's copy the categories and let's paste them and let's rename them to transactions. So we are doing this inside of the app folder API route right here. We have the transactions. So let's go inside of route.ts right here. And let's go ahead and chain slash transactions. And we have to import transactions. So import transactions from dot slash transactions and add them here. There we go. So as always, if I go ahead into my localhost 3000 slash API slash transactions, it's now going to load categories instead, right? Because we have not changed anything. So let's go ahead and slowly do that. So I'm going to go inside of my transactions, and this is where we are going to work. So first things first, let's remove the categories and insert category schema entirely. And instead, let's add transactions and insert transaction schema. There we go. And now we're going to go ahead and resolve each endpoint one by one. So the first one is this one. And it's immediately going to be different in a way that the simplest get request will have some param options. So we add a Z validator here. And we are specifically going to validate the query, which is going to be optional. So this is going to be Z dot object. Let's just ensure that we have Z imported. We do. Great. So it's going to be Z.object, which will accept the property from, which will be a string. And it's going to be optional. Then we're going to have two, which will be a string again and optional as well. And we're also going to have an account ID, which will be a string and optional as well. So as you can see, we will be able to filter our transactions by dates and by a specific account ID. But we don't have to do that if we don't want to. Great. So our authorization stays the same. But let's also do one more thing in here. Let's go ahead and let's use C request valid query. And let's destructure everything we need. So from to and account ID like that. Perfect. And now in here, we're going to go ahead and we're going to find const default to be new date. So in case we don't have any filters from and to past, we don't want to load the entire history of transactions. That can be a huge amount of data. So we're going to default. If nothing was passed, we're just going to default by the last 30 days. That's how most of this financial managers work. They always show you the last 30 days of data. And now we're going to write const default from, and we have to find a way to deduct 30 days from the new date. So we can do that with a library, which we are going to use throughout the project called date FNS. So npm install or bun add date FNS. Let's ensure we have that. Great. And now we can use sub days, which I'm going to add an import for in a moment. So this is going to be subdays from default to 30 like that. Let's go ahead and import subdays from date FNS. There we go. Perfect. We have default two. And now let's write const start date to check if we have from. In that case, we're going to use parse from date FNS. So make sure you've added parse import here. so we're going to parse the from value in a format of lowercase y dash mm dash dd like that and new date like this and let's go ahead and do it like this so we have more space so if we have it that if we don't have it it's going to use the default from now let's define the end date here to check if we have two. If we have two, it's going to use parse two, and it will be exactly the same as this one. So we can copy that. Otherwise, it's going to use the default two. Like that. There we go. So let's go ahead and just confirm one more time that this is correct. We have default two, which is used as the end date and default from as the starting date. Perfect. So that works fine. And we are parsing it in this way because that's what our drizzle is going to accept. Great. So let's go ahead and modify this now. So first of all, well, I think it might be easier for us to actually build everything from scratch. So .select, we are specifically going to select ID, which is transactions.id. We're going to select date, which is transactions.date. Category. Now, this is the interesting thing, right? So if we write just category ID to be transactions.categoryID, this is fine, right? But on the front end, we are just going to get the ID, right? That's not something we can work with. We don't want to display the user a random ID. I want to display the name of the category. So I can do that by adding categories from database schema.name. But this by itself will not just work. So let's go ahead and make sure you have all the imports. You need transactions, insert transaction schema and categories for now. So this by itself will not just work. We're going to have to join the categories later. Great. So that's why we are doing both. We are doing category ID so that we can, well, find that category in the array of categories, right? Because category name doesn't have to be unique. So it doesn't give us too much information except presentationally wise for the user. So we need both. And let's go ahead and go further. Let's add the payee to be transactions.payee. Let's go ahead and add the amount. After the amount, let's add the notes. And then we're going to have the account ID here. And the same way we did it with the category, we're going to have the account itself come from accounts schema.name. So just make sure you've added the accounts alongside categories here in the schema. So we are going to select all of that from transactions. And now we have to perform an inner join and we have to perform a left join. So for accounts, we are going to use the inner join because this will ensure that the both sides of the table relations exist. So we don't want to load transactions which don't have this account ID. So let's go ahead and do the following. So we're going to write inner join accounts. We already have that import. And then we need the equals. So let's just confirm that we have equals. We do have from drizzle ORM. So accounts has to equal transactions.accountID, and it needs to match accounts.id like that. So that's the inner join. We need to ensure that every transaction has a matching account ID. But for the category, we're going to use the left join. So for this, we're going to write categories, and it's going to be very similar. So equals transactions.categoryID, categories.id. So in case a category doesn't exist, that's fine. We are still going to load this transaction, right? We are just not going to match any category ID or category name. And that's fine because category is not required, but account is required. So we are using an inner join, which means if there is a transaction, which for some weird reason somehow doesn't have an account, we are not even going to find it here. So at least that's, I'm not a backend developer in my career. I was a frontend developer. So I'm learning this the same way you are here. So if you think that I explained this in the wrong way, feel free to leave a comment, right? And see how you would explain it. And now let's chain a where method here. And now we need to use our end operator. So just ensure you have end. There we go. So first of all, we're going to check if we have an account ID. If we have an account ID, in that case, we only want to load transactions with that account ID. So we ensure that it matches that variable. Otherwise, to skip this, we need to add undefined like this. So that is the optional filter. And now we're going to add some required filters. For example, accounts.userID needs to match out user ID. So if we didn't add this inner join for the accounts, we would not be able to perform this filter right here. So remember, inside of our schema, transactions don't belong to users, right? Transactions belong to account ID. So that's why we need to perform an inner join on the accounts so that we can check if that matching account has the matching user ID of the currently logged in user. So that's how we ensure that only the user's transactions are being loaded. So that's the first one. And then we need a greater than or equals. I think that's what this is a shorthand for greater than or equals. I prefer, let's see, greater than or equal. Yes. So greater than or equal. And we're going to look at transactions.date. So when it was created to, and compare it to our start date. and then we're going to copy and paste this but this time we're going to use less than or equal with the end date and we need to import less than or equal from drizzle orm there we go perfect and now we also need to add an order by method right here so we are going to order by descending. So we need that. So import descending from a drizzle ORM. Specifically, we're going to work with transactions.date. So we're going to, by default, order them by date. There we go. So that is our query. So you can see how I wanted us in this course to learn more about working with SQL itself rather than using the Queries Prisma-like operator, which is obviously\neasier to work with, but we don't really know what's going on there, right? So this time we are kind of forced to understand this inner joins a bit better. I think you can even hover over the inner join. And in here, you can read the documentation. So executes an inner join on the operation. Calling this method retrieves rows that have corresponding entries in both joined tables. So that's exactly what we need for accounts. But for left join, for example, calling this method associates each row of the table with the corresponding row from the join table if a match is found. If no match row exists, it will set all the columns of the joined table to null. So that's fine for us because that's exactly what suits the categories field here because it is optional. Perfect. So yes, if you're confused, looks like they have great documentation here for this joins. You can just read them. You even have some examples, it seems here. Amazing. Great. So now that we resolved that, let's go ahead and let's resolve the individual ID fetch here. So what I'm going to do is I'm just going to copy this select because I think we're going to need it here. So the querying can stay the same, right? We have the ID, the missing ID, and unauthorized. We're not going to have any querying here. So what we're going to do is we are going to, yeah, the problem is we no longer have those errors, right, for categories. So we're to have to be a bit more careful. So in our get ID for the transactions, this is what we are going to select. So we needed the ID. And we also, oh, I think I forgot a very important field here in my first, in my get route here. So let's go back inside of here. So after ID, make sure you add date. I completely forgot that we have to return the date, right? We want to show the user when this was created. So make sure you add date inside of here. So let's copy this again this time. Let's go to the bottom here. Well, not to the bottom, but just to the next one. So let's see, we have ID, we have a date. And we don't have to return the category name this time because this field right here will be loaded inside of the drawer, inside of the form. And form can simply detect the category ID and it's going to display the label by the select component. So we don't need the category name. And same is true for the account name. So we just need the ID, the date, the category ID, payee, amount, notes, and account ID. So again, we are working from, this time it's going to be from transactions. And let's go ahead and let's perform the same inner join here for my accounts. So I'm going to copy that and add it here. So inner join with the accounts. So we only ensure that, so we ensure that all these transactions have the accounts and so that we can add some filters here. And that filter is, well, primarily going to be this. Whoops, my apologies. Let's go to the ID. So primarily we are querying by the, not categories ID, but transactions ID. And then the second argument is going to be accounts.userID. So be careful with this, right? Make sure you don't have any misspellings here. So inside of the where here, we are querying by the transaction ID because we have the ID as the value here. But we are also ensuring that this data that the user is trying to load exists in this inner join of the accounts and it has a matching user ID of that account. Great. So if there is no data, we return a not found here. Perfect. And now let's go ahead and let's define our post method here. So this one is going to be a bit simpler. So inside of here, we're going to do the following. We're going to add insert transaction schema, but instead of using pick, we're going to write dot omit. So we want everything except the ID field. That's not something I want the user to pass, right? So the values are fine and this is fine. And we are going to work with database that inserts transactions, so not categories. The ID will stay the same and the user ID doesn't exist. So we remove that. There we go. Perfect. That is fine right here. And now let's go ahead and let's perform a bulk delete right here. So the Z evaluator for JSON will stay exactly the same. And now we have to do something called, I'm not sure what's the right expression. I think it's called a chained query. So we need to find a way to add a very specific... We can't just query inside of this where to confirm that these transactions we're trying to delete has a matching account with a matching user ID, right? It's a bit complex. But Drizzle ORM does have a solution for that. So if you go inside of the documentation or the delete option, so let me show you here. So access your data, delete. They have something called with delete clause. So using with clause can help you simplify complex queries by splitting them into smaller subqueries. So they are called common table expressions. That's the name. All right. So that's what we have to do. We have to add this dot width so that we can make this where query be powerful because we want to be careful what the user can bulk delete, right? This is a powerful API endpoint, and we need to ensure that it's protected. So this is what we are going to do. Let's go ahead and do the following. I'm going to go ahead and remove this returning. And for the delete, I'm just going to ensure that we are deleting transactions here, right? Now, here's the problem. We cannot query where here at the moment, because what we have to do is the following. We need to write const transactions to delete. and let's write that to be our database.with and let's go ahead and define this as transactions underscore to underscore delete dot as database.select. We are only choosing the ID from the transactions.id from transactions. we are going to do an inner join on the accounts where transactions account ID matches the accounts.id where we are going to let an end query inside immediately. There are the values, there are our IDs, which we send here, IDs, are in the array of transaction IDs. So transaction, my apologies, this transaction ID is in the array of our IDs that we are passing. My apologies, it's a tongue twister for me. Okay, so we ensure that we are only looking for transactions which have been passed in these IDs right here, right? But then we also ensure that accounts, user ID are with the currently logged in user ID. That's important for us. So we are not going to load these transactions if the user ID is not matching. So what this will create is it will create a list of IDs for us to query here. So we're going to add before the delete, we're going to add with transactions to delete like this. And then we're going to add where in array transactions.id. and then we're going to use something called a SQL operator here from drizzle.org so that we can write raw SQL. So that is here. I'm going to move this to the bottom. There we go. This one. We're going to go ahead and open back text, open parentheses, and we're going to write select ID from, and we are going to inject the transactions to delete right here. So let me show you this in one line like that. There we go. And then we're going to add returning here, id transactions.id. So let's go ahead and let's go over this again. So we are attempting to delete transactions, but we are only going to remove transactions where the ID matches this list of IDs. And what is this list of IDs right here? Well, that is a query which will join all the accounts. So first of all, we are only going to load transactions which have accounts. And then we're going to ensure that it only loads transactions which have been passed in these IDs values. And then it also ensures that it's only there where it has a matching user ID. Great. So that's what we are going to do here. Perfect. And now what we need is we need to resolve our patch request. So let's go ahead and do that. So very similar to post, it's not going to have insert category schema first. It's going to have insert transaction schema. Instead of pick, it's going to have omit and it's going to omit ID like that. There we go. So let's go ahead and do the following. So what we're going to have to do now is we're going to have to copy this transactions to delete because we have to use a similar query now. Because remember, transactions don't belong to the user. So we always need to do this deep querying by finding the matching account with the user ID, right? So make sure you are in your patch right here. You've changed insert transactions right here. You validated the ID, the values, everything stays the same, but we don't need this at the moment. Or let's keep it simple. Just do it like this for now. Let's go ahead and paste the transactions to delete from our bulk delete above. And we're going to call this transactions to update here. And we're going to change this to be transactions to update as well. So this can be the same. So select transaction ID from transactions, inner join of the accounts, where in array, and this is the cool thing. So we're not going to do in array here because this is not bulk update. This is just individual update. So we're just going to do equals values.id. Actually, it's not values.id. It's directly id because we destructure it right here from the param. There we go. So that's the transactions we can update. So only the one with the matching id and where our user id has a matching account user id. Great. And now inside of here, we can go ahead and chain dot with transactions to update dot update transactions because that what we are updating set the values which are going to match perfectly because of our ZOD validation here And we're going to do where in array transactions.id match the SQL open parenthesis. select ID from transactions to update. Again, this is how it looks like in one line. There we go. And returning like that. There we go. So this now works fine. And we are safely updating our transaction. Right. So now we can go ahead and make this kind of easier for us. We can copy this again. Transactions to update here. And this is the last one to delete by ID. Right. So the error handling here is fine. Let's immediately add this instead of transactions to update. This will be transactions to delete. So transactions to delete here, change it as well. And this is actually exactly the same. Right. Because this is just ensuring that the array of IDs that's going to be generated for our where query is only of those transactions which the user should have access to based on their account user ID. So we have that transaction to delete here. And now inside of here, let's go ahead and let's remove this entire query. So first of all, with transactions to delete, we are deleting transactions, not categories as it was previously. where in array we have transactions.id and we have a SQL, open parenthesis, transactions, oops, select ID from transactions to delete. There we go. And let's also chain returning inside, but we are only going to return ID, transactions.id. there we go so if that was not found it means we probably didn't have access because we could not find it in this array which only loaded by that id and by our accounts perfect that is it so here's what i want to do i just want to ensure that we don't have any crucial bugs for example so i'm going to write dot from here and i just want to check that we are not accidentally loading something from accounts or from categories, right? Because we copied this from categories. So that's fine. Let's do .update, transactions. That's fine. Let's do .delete, transactions, transactions. Perfect. I think our API endpoint is ready. Great, great job. And see you in the next chapter. Now that we have our API endpoint for transactions ready, let's go ahead and let's convert that API endpoint into a set of reusable query hooks. So I'm going to go inside of my features folder and I'm going to copy and paste the accounts and I'm going to rename it transactions. I'm only going to focus on the API folder for now, starting with use get accounts, which in this case is going to be use get transactions. Let's rename the hook useGetTransactions And now let's go ahead and let's import useSearchParams from nextNavigation And inside of here let's get the params using useSearchParams Let's get the from using params.getFrom or an empty string To is going to be params.getTo or an empty string and const account ID is going to be params.get account ID or an empty string as well. Now we're going to change the query key to be transactions but only for from to and account ID like that. And now we're going to modify this API call to go to transactions. we're going to open the get request and we're going to add a query here to have from to and account id and our error is going to say fail to fetch transactions i'm also curious about this right here now that i think about it perhaps it might not be a good idea to make our key unique based on the query. So I'll just add a to do check if params are needed in the key. We're going to come back to this later, but this worked fine in my original implementation. But I'm a bit curious about it. But we will explore that later. Now let's go ahead inside of the individual use get account and let's rename it use get transaction. So inside of use getTransaction right here. Let's rename the hook, use getTransaction. And the ID prop is the same, but the query key and the API and the error should all be transaction. So this should be individual transaction and ID. And this API request should go to client API transactions ID. and this should be failed to fetch transaction. There we go. Let's go ahead and let's use the useEdit account and let's rename it to useEditTransaction. Let's rename the hook, useEditTransaction and let's replace all instances of accounts. So starting from the response type into the request type, the mutation function here and let's just leave it like this for now. So just these three instances and let's change it to transactions like this. So client API transactions, client API transactions, client API transactions, just like that. The JSON here is correct. We don't have to worry about that because we're using the correct response and request types from transactions here. Now what we are going to invalidate here, First of all, let's fix the success message. So it's going to say transaction updated, and this will be failed to add a transaction. We are going to invalidate the transaction ID key, and we are going to invalidate all transactions here. So this is why I'm curious about our use get transactions here. So I'm not sure if I invalidate just the transactions, will that invalidate with this as well? I'm just not entirely sure. So we will see that later. And inside of here, I'm just going to add to do invalidate summary, because we will need to invalidate summary here later. And now let's go ahead inside of the use delete account. And let's first of all, rename it to use delete transaction. So individual. And inside of here, let's first rename this to use delete transaction. and we're going to change this to use api.transactions. So we have the proper response type and so we have the proper, well, API call here. So this will be transaction deleted. We are going to refresh the individual transaction with this ID and we are going to refresh transactions multiple. and we are also going to invalidate summary later on and this will be failed to delete the transaction. Now let's go ahead and let's go inside of use create account. Let's rename it use create transaction. Let's rename the hook use create transaction and let's do the same thing for our API instances to all go to transactions. So response type, request type and the API call. This is going to say transaction created and it's going to invalidate the transactions. And this will say fail to create transaction. There we go. And I'm going to add a to-do. It's going to say invalidate summary later when it exists. And let's go ahead into the last one here, which is use bulk delete accounts. Let's rename it to use bulk delete transaction. Transactions multiple. Let's go ahead and fix the hook name. And again, we are changing all instances of accounts. So these three into transactions. So response type should be this. Request type should be IDs. and what's important is that in here we are calling the transactions bulk delete. So this is going to say transactions deleted and we are going to invalidate transactions. And we are gonna say also invalidate summary here and failed to delete transactions. There we go, perfect. But there is one more hook and API endpoint that I want to add here. So we will be able to add transactions in bulk thanks to the CSV file upload. So because of that, I want to revisit my app apitransactions.ts. So we have the get all, we have get individual, and in here we have post to create an individual, So I mean, here we have post to create, to delete multiple. So in between those two or at the bottom, it really doesn't matter. Add another post, which will say bulk create right here. And now let's go ahead and let's resolve this bulk create. So first of all, we are going to protect it with clerk middleware. And of course, you're doing things in the transactions, right? we add the clerk middleware and then our z validator is going to be the following so it's going to validate the json which is going to be a z dot array so it's going to be an array of insert transaction schema dot omit id true so very similar as our post request as you can see but instead of just accepting a single JSON object, this time we accept an array of those objects. Great. So after the validator here, let's go ahead and let's open our asynchronous controller and let's go ahead and get the out using get out. Let's get the values using C request valid JSON. If there is no out user ID, let's add a question mark here. In that case, we are going to return C dot JSON with an error of unauthorized. And it's important to add a status code. So we infer the correct response types here. And now very simply, we get all data. So no need to destructure the individual because we are creating an array, right? So await database.insert into transactions values. And instead of values, we're going to add values.map, get the individual value, open an immediate object, use the ID to be create ID method, and spread the individual value inside. And just add chain.returning inside. And right here at the bottom, we're going to add return C.json and pass in the data. There we go. So let me just see if I can indent this. Yes, I can indent this one, but these ones should not be indented. There we go. So what this is going to do is it's going to return an array of those created transactions. So inside of values, we are passing in an array of objects. So you can do that natively in SQL or Drizzle ORM in this case. Great. So now that we have this bulk create endpoint, let's go ahead and let's add a hook for that. So I'm going to go ahead and close everything. Let's go inside of features, transactions, API. And I'm going to go ahead and copy and paste use delete transactions. And this will be use bulk create transactions like this. So let's rename this. Instead of use bulk delete, it's going to be use bulk create transactions. And instead of bulk delete here, we're just going to say bulk create. So our response type here will either be an error or data. And our request type here is going to be an array of objects which are necessary to create a transaction. So now we have to modify this as well. It's not going to be bulk delete, but bulk create. There we go. So this is how it looks like in one line. And instead of transactions deleted, this is going to say transactions created. And we are going to invalidate the transactions and A2D can stay the same. And this will be failed to create transactions. There we go. So that is it. We successfully added API endpoints, sorry, API hooks for all of our API endpoints. So in the next chapter, what we are going to do is we are going to prepare this transactions page right here. and we're going to create the form to create a new transaction. So it's going to be a little bit more complicated because we're going to have to create a select component which will be able to, well, search for existing categories and existing accounts. But also we're going to do it so that the user can easily create a new category or account from that select searchable field. So that's going to be for the next chapter because it's a module of its own and it's a fairly complicated one. Great, great job. Now that we have converted our transactions and points into usable hooks, let's go ahead and let's create the transaction page and let's create a drawer which is going to open so that we can create a new transaction. We're going to start by going inside of features, transaction hooks right here and let's change this use new account to use new transaction. inside of here I'm going to also rename the function so I'm going to rename all instances of account to transaction and the rest can stay the same and then inside of components let's go ahead and let's rename new account to new transaction and let's do the same rename inside of the component. So it's not going to be new account but new transaction. Now let's go ahead and find the insert account schema import and let's remove it so we know where we have an error here and let's import insert transaction schema here instead. So this will be a slight modification. let's use this insert transaction schema but instead of pick name it's going to be omit id true like that so now we have this error here for the values so let's go step by step first let's resolve this we are no longer using use new account so find that import and remove it in favor of use new transaction which we've just renamed like that next we have use create account Well, first of all, let's use new transaction here. And in order to fix these values here, we have to change this mutation. So right now it is use create account. Let's remove that in favor of use create transaction. Again, from add features transactions. Like that. So we created this in the previous chapter. And let's use it here. and I have some type script error. I think if I just reload my window, it's going to go away and there we go. We no longer have any errors here besides in our form here. So that's why I'm going to remove the form for now and just write a paragraph to do transaction form. Let's change this to say new transaction and let's change the description to say add a new transaction. There we go. And we can remove the import from account form as we are not going to need it at this moment here. So we can just leave the on submit function be unused. Now let's go ahead and close everything. And let's go inside of our providers, sheet provider. I'm going to add a little space here. And let's import new transaction sheet from features, transaction components, new transaction sheet. So the component we just modified. make sure you have renamed it here otherwise you won't get an import so I'm gonna add it here there we go and now it can be opened so now we are ready to go ahead and go inside of app folder dashboard and let's create a new folder transactions here actually what we can do is we can copy the accounts that's easier so let's go ahead and copy accounts and write transactions like that let's go inside of page.tsx and let's rename this and the default export to transactions page. There we go. So now if you try and go into transactions, it's going to load accounts because we just copied that. Again, if you are getting a 404 here, confirm inside of your navigation that your transaction href matches the new folder we've just created transactions here. make sure there are no typos or misspellings great let's go ahead and let's modify the this from accounts page to transaction history and let's go ahead now and slightly modify the rest of the stuff here so what i'm going to do is first of all okay we have some typescript errors again this is just some weird typescript server which happens when i copy a lot of stuff right So when I just reload my window or shut down VS Code, they go away. So let's start with this new account, use new account. We are no longer using that. Instead, we're using use new transaction from features, transactions. So let's replace that here. And instead of new account, it's going to be new transaction. And the errors are going to tell us where we need to change it. So right here, new transaction. There we go. So now if I go ahead and click add new, there we go. new transaction to do transaction form. Perfect. So that's exactly what I wanted to achieve. So for now, we can leave everything else as it is. We can leave use bulk delete accounts, all of these things. We're going to come back to this later. Because what I want to wrap up now is what we started. And that is the form. So go back inside of features, transactions, components, new transaction sheet. So first of all, before we can even start creating this component, we need to load some options for it, right? So I don't want to use the form component to load options inside. I want to use this parent component, right, which will render the form to prepare all the options. So what options am I talking about? The category options and account options. So that's going to be shown in a drop-down, but we first need to fetch it because every user has its own options. So let's go ahead and do const category mutation to be use create category. So that's going to be from features API use create category. I'm going to add that here and I'm going to separate the features so it's visually more pleasant to see. Then let's write const category query here to be use get categories. Again, I'm going to align my imports here. And then we're going to have const on create category to be a very simple method which accepts the name, which is a string and simply calls the category mutation dot mutate and passes in the name. And then we're going to have const category options, which are going to be a very simple check of category query dot data question mark question mark empty array. So if there is no data we going to work with an empty array and then we can safely do dot map here We can get the individual category and we going to transform it by returning an immediate object from this dot map into label category name and value category dot ID There we go. So you should have the name, and you should have the ID. That's why inside of our categories, API route, when we get all of them in here, we are returning the ID as well as the name because we need it right here. There we go. So now we can copy and paste this and we can do the same thing for account query. So this will be account query. Use get accounts. Let me go ahead and fix this import here. So I'm going to add it to the top here, but I'm going to separate it from categories. All right. So let's see which ones are the originals. This is the original. This is the new one. So the second one is not category mutation, but account mutation. and that will be use create account. So let me go ahead and align the import here as well. So I have them all in one place. Then we have on create account to use the account mutation dot update and name is the same. And lastly, we have the account options which will use the account query dot data and this will not be category but account. There we go. So now we are ready to pass these as values for our form. But we don't have the form yet. So let's go ahead and let's prepare the form. So inside of account form, let's change this to be transaction form. Like this. And then let's rename the component itself to also be transaction form. Like that. And now we can go back inside of the new transaction sheet component and we can import the new transaction, my apologies, transaction form. ROM dot slash transaction form. But I am going to change that to features transactions components simply so I can keep all of them right here together. There we go. Great. So now that we have that, let's go ahead and let's pass in all the props that we now have inside of here. So that's going to be on submit, on submit. It's going to be disabled. Let's put it false for now. We're going to have category options, which are going to be category options. Then we're going to have on create category to be on create category. And then we're going to have account options to be account options. And onCreateAccount is going to be onCreateAccount. Like that. So we're going to have to adapt our transaction form a bit to accept all of those. And first, let's resolve this disabled state. So we can do that by introducing a constant is pending. And it's going to check if our first mutation here, so this mutation, Let's rename it createMutation actually. So it's more implicit. Again, I have to reload my window so it restarts the TypeScript server. There we go. So I just renamed this to createMutation. And now inside of my onSubmit, I'm also going to change this to createMutation. And now inside of isPending, let's add if createMutation is pending or if categoryMutation is pending or if account mutation is pending like that so that's our is pending here and let's now add const is loading so is pending will disable the form whereas is loading will not even show the form so we're going to show the loading status if we are loading our category options So we're going to do category query is loading or if account query is loading like that. So now we can do the following. We can replace the disabled with is pending and we can also do the following. We can check if is loading. In that case, we're going to show one thing. Otherwise, we're going to show this transaction form. So let's move it inside of the other if clause or the else, right? And now in the first one, we're going to add a very simple div with a class name here, which is going to be absolute in set zero, flex items center, justify center. And inside, we're going to use a loader2 from Lucid React with a class name of size4, text muted foreground, and animate spin. so just ensure you've added loader 2 from lucid react i'm going to keep it here at the top with my zod and let's see if i can just align this better actually this is fine great so now we are ready to show our transaction for me you should now see how let me go ahead and if i refresh here and if I click here, well, it loads immediately, right? But if it weren't loaded, right? If these options were not ready, the category options and the account options, then it would show a little loading indicator before it loaded the form. So now let's go inside of this transaction form, which we've just created. So transaction form, and we have to modify, well, first of all, the form schema, and then we have to extend the props a bit. So let's start with the form schema. So the thing with form schema here is that we have to kind of create it, we have to modify it a bit. So it's actually easier for us to write it out from scratch than to try and modify the one from drizzle. Because there aren't exactly options to override in the way we specifically want to. So let's go ahead and do this all over again. let's write manually date to be z.course date account id will be z string category id will be z string nullable and optional payee will be z.string amount will be z.string and notes will be z.string nullable and optional. There we go. So this is our form schema here. So I believe that now the values seem to still not match, but okay, let's go ahead and let's just wrap things up here. So yeah, now what I want to do is I want to have actually two schemas here. So we're going to have form values, but we're also going to define type API form values, and that's going to be z.input, and that's going to use type of API schema, and we're going to create API schema just below form schema. So const API schema is going to be insert transaction schema. So the one we have from database schema, Now we can remove insert account schema here. And inside of here, we are just going to omit ID true. There we go. So this way we have, well, we are going to need this manually. I know it's a bit confusing because we didn't have to do this in the previous ones. Well, that's because these options are just so specific that it's easier for the types to have two schemas here. So now we have form values and we have API form values. And we're going to do the following. We're going to change the default values to use the form values, but the onSubmit will use the API form values. And I think that now, there we go, onSubmit no longer has an error. Now we have these errors. We're going to resolve that next. Great. So onDelete is fine, disabled is fine, and now we have the account options, which I want to be an array of objects, label, string, and value, string. like that. And then I'm going to copy and paste that and call this category options. And then let's go ahead and create on create account to be a void which accepts a string. Let's copy and paste that and call this on create category. There we go. And once I save this, I believe we should no longer be having any errors inside of our new transaction sheet right here. So we are completely fine to just continue working in the transaction form itself. Let's continue here and let's add the account options, the category options, on create account and on create category right here. And now what we are going to do here is, well, we are going to have to do some modification here, obviously, because of the type error. But before we can start working here, we have to install a component called react select. And then we have to create our reusable select component and style it according to the existing styles. So let's go ahead and do the following. Let's go inside of our terminal here. And let's do bun add react select or npm install react select depending on what you use right here and now I'm going to go ahead and close everything so we have some errors here that's fine but let's go ahead and go inside of components for now and let's create our select.dsx component so make sure you don't put it inside of UI because we're going to have a separate select component which will be here. There's going to be the chat CN one, but now we just want our own here. So let's mark this as use client and let's import use memo from react. Let's import a single value from react select. And let's import creatable select from react that select slash creatable. I like this. Let's define the props here. So the props are going to be on change, which will take in the optional value and return a void. And on create, we'll do the same thing. But on create can be optional, but the value is required when we are creating something, if we are enabling that. Options itself are going to be optional, and they are going to be an array of objects, which are in format of labels.\nString and value string. So exactly what we are formatting in our sheet and our form component. Value can be optional. It can be string, null, or undefined. And disabled is a Boolean. Placeholder is an optional string. So if you're wondering why did I put both the question mark and null and undefined, it's simply to satisfy the types, right? Because I've already coded this project before, and I know that the types are going to not work with just this, but in this combination, it works fine. Let's go ahead and let's export const select component here. And let's destructure from the props, the value, the on change. Let me scroll up so you can see. So value on change, disabled, on create, options. And let's go ahead and ourselves define the default as an empty array. And let's add a placeholder. And I believe the errors will go away after we add an opening of the method. Inside of here, we are going to return the creatable select component, which is a self-closing tag. And now let's create some methods here. So on select, we'll accept the option. And the option is going to be the following. So I'm going to collapse this like that so I can write the type in full line. So it's going to be the single value, which we added an import for. open pointy brackets and inside write label string value string and that is going to be our only prop which we accept and inside of here we're going to call on change to be option question mark dot value because it's optional right the single value itself can be null so we imported single value from react select so it matches our custom on select and then we can safely pass it here. And let's go ahead and do const formatted value here to be use memo. And let's return options.find. We find the option using the matching value. There we go. And inside of the dependency array of use memo, we add options and the value. So that way we know which value is currently selected because we are going to pass the ID. So we have to find it in the array of options. And now inside of here, let's go ahead and pass in all the necessary props like placeholder to be placeholder. Last name, text small height 10. So in this, I'm just matching the styles which are in the input component, right? So I think inside of here, you can find height 10 and text small, right? So I'm doing that here instead. And some styles we are going to have to change like this. So React Select has its own API for styling. So instead of here, we are going to get control, get the base, open and return an immediate object, spread the base, which basically means keep all existing styles except border color, which we are going to change to be E2E8F0. And on hover, we're going to change the same thing. So border color and just copy this again like this. Then we're going to pass in the value to be formatted value. And on change is going to be on select. And options are going to be options. On create option will be on create. My apologies, not on create options, but on create. option individual and is disabled is going to be our disabled prop. So now what we've created is a generic reusable select component. So because of this generic naming and props, we can use this as a select component, which will be able to list and use to create new categories, new accounts, whatever we want in the future, right? So it's very, very modular. That's what we wanted to achieve here. Now we can head back inside of our, where is it? Inside of our features, transactions, components, transaction form. And now let's go step by step. So first of all, I'm going to log this. And instead, we're just going to go ahead and log the values for now. Because we're going to have to do some modification with the values and you're going to see why later. So let's start with accounts, right? So let's go ahead and choose for the first field to be account ID here. And let's change this to be account. So the user is choosing the account and it's not going to use input. It's going to use our new select component from components select. So just make sure you've added an import for that. I'm just going to move it here. We are going to have input as well, so don't remove it. So we have the select here. So let's go ahead and give it a placeholder of select an account. Let's give it options to be account options. Let's give it on create to be on create account. Value to be field dot value. And let's give it on change to be field on change. and disabled is going to be disabled like that. So I think that we can already see this in action. I'm just going to refresh my local host to ensure that the server has hot reload up to date. So now let me go ahead and refresh one more time just in case. There we go. When I click add new, I have select a new account and there we go. It loads cache. And if I go ahead and type new account and press enter, you can see how the form is disabled. And we just reused our hook, which you can see immediately added the account here as well. So yeah, okay, this is an accident, right? Because this data table is loading accounts. So ignore that, right? Don't forget that we are loading accounts here because we copied and pasted from accounts. But as you can see that it's here as well. So now when I go here, you can see that I have new account. If I write test again, it's immediately added here and it's immediately added here. Perfect. So that's what I wanted us to do with our nice reusable select component. Beautiful. And now let's go ahead and let's copy this form field entirely. Paste it just below this, above this button here. And this one is going to control the category ID right here. So this will be choose a category. And it's going to work in exactly the same way. So select a category. So category options. On create category. and this, I believe, is exactly the same. So let's go ahead and check that out. So I'm going to refresh everything again, and now inside of here, there we go, I have food, but if I go ahead and select something new, there we go, it's created right here, and if I go into my categories, it is right here, created. Beautiful. So you can see how powerful those hooks are because we can easily add them everywhere, and we have the type safety, so we are confident in our code, and we know that we are handling this in a correct way. beautiful. So now above these two fields, I actually want to start with not the account or the category. I want to start with choosing the date. So for that, we're going to have to create a component called date picker. ChatCN does have a component called date picker, but you cannot install it like any other components. It's similar to our form component. We need to use popover and calendar to install it. So this is what we're going to do. We're going to add those two individually, and then we're just going to build our own because ours is going to slightly be different from the ones they have in their example. So let me go ahead and just go inside of my terminal here and do BonaX, ChatCN UI latest, and let's add calendar. And besides the calendar, let's also add the popover component right here. So let me just confirm that I have everything I need here before I run my app. So inside of my package.json, I think that now I should have installed a React Day Picker. So I think that adding the calendar automatically installed that. So just confirm your package.json that you now have a React Day Picker component here. Great. We now have the popover and the calendar and the React Day Picker. So let's go inside of components and let's create our date picker TSX component. Let's go ahead and import everything as React from React. Let's import format from date FNS, which we already have installed because we needed it previously. Let's import calendar from Lucid React, but let's do it calendar as calendar icon because we are also going to have a component called calendar. And now let's import select single event handler from React Day Picker. Let's import CN from lib utils. Let's add our button component from add slash components UI button. And here we have the calendar component from dot slash UI calendar or components UI calendar. and lastly we need to import all the things from our new popover so that's going to be the popover the popover content and the popover trigger component there we go now let's go ahead and let's define the type props here so we're going to have value to be an optional type of date on change is going to be select single event handler and disabled is going to be an optional boolean. Let's export const date picker. Let's go ahead and assign these props here and let's also destructure them. So we're going to have value on change disabled. Now inside of here we're going to go ahead and open the popover component. So this should be popover. Inside of here, we're going to add the popover trigger component, which is going to have as a child property with a button component inside. We're going to give some attributes for this button, starting with the disabled to be disabled. Then we're going to have variants to be outline. And then we're going to have a class name we use CN in the default classes it going to have full width justify start text left and font normal And then dynamically if there is no value we going to show text muted foreground to indicate to the user that date has not been selected. Then in here, we're going to use our calendar icon with the class name of size four and then are of two. Let's just properly spell class name for JSX components. and inside of here we're going to check if we have value we're going to use our format method otherwise we're going to return a span pick a date in the format method we're going to pass in the value in a format of ppp like this and then let's finally add the popover content and inside of here let's add a calendar component which is a self-closing tag so inside of here we're going to give it a mode of single we're going to give it a selected of value we're going to give it on select to be on change and we're going to give it disabled of disabled and initial focus like this And I forgot to add return around here. So this would never actually render if we don't add that. And there we go. That is our date picker component. We can now head back inside of our features, transactions, components, transaction form. And let's go ahead and let's import our date picker from components, date picker here. And I'm going to go ahead and copy the form field here. and I'm going to paste it above. So this is going to control the date. So I'm going to write, well, we don't have to say anything here. So we can remove the form label entirely. And in place of select, we're going to add our date picker component. Let's give it a value of field dot value and let's give it on change to be field dot on change. And let's give it disabled to be disabled. Like that, there we go. So let's ensure that we have our app running. For example, I don't. And let's try this out. So bun run dev or npm run dev, depending on your package manager or your runtime. Let's wait a second for localhost to spin up. And now if I go ahead and try and create a new transaction, I have pick a date option. There we go. Perfect. And when there is no selection, you can see how it's kind of transparent to indicate to the user that nothing has been selected. Great. So we have that and we have category ID and account ID. Now let's add a simple one for the payee. So I'm going to go inside of my transaction form and I'm going to go ahead and copy the last one category ID and I'm just going to paste it below. So this one is going to control the payee field. So we're going to write payee here. And instead of select, we're going to have a good old input component. So just ensure that you haven't accidentally removed the import for the input component here. Okay, let's just scroll here to the payee. Inside of here, we're going to give it disabled. If it is disabled, we're going to give it a placeholder, add a payee, and we're going to pass in the field property. We're going to spread the field property. So payee is just going to be a plain text, right? Anything can be inside. Great. So what I want to create next is let's go actually with a simple one. Let's first add the text area here. So let's do Bunex, Shadzian, UI, latest, add text area. So this is another component that we need for our notes. So add this. And now we don't have to modify anything here. So we can just copy and paste the form field for the payee. And this one will control the notes. So let's write notes here. Instead of input, this is going to be text area from components UI text area. So just ensure that you've added this import right here. And now what we are going to change is the following. Let's move the field to spread here. So we overwrite everything after it. So it's important the position of where you spread the field. So move it here and then add value to be field dot value or empty string like this. And the placeholder is going to be optional notes. If you refresh your localhost, every time you shut down your app, you have to refresh it again, even though you have localhost running, right? Because the hot reload has been broken. All right, so let me click add new. And there we go. We have optional nodes and we have payee. Perfect. So what's missing is the amount component. So that's what we have to develop now. Let's start by adding a couple of packages required for the amount input component, which is next. So first of all, let's do bun add or NPM install react currency input field. If you want to, you can of course develop your own, right? But this is a very good one. It has the perfect masking that I want. And I can easily make it look like ShatCN UI component. So it works perfectly fine for me. And then let's do banx ShatCN UI at latest. And let's add a tooltip component here so that we can indicate to the user some useful actions. And let's do ban run dev. So I'm going to go ahead and close everything and go inside of my components and create a new amount input.tsx. Let's go ahead and let's import the currency input from React currency input field. Let's import all the icons we are going to need from Lucid React. So those are going to be the info, the minus circle, and the plus circle components. let's prepare our cn and now let's add from components ui tooltip everything we are going to need so we're going to have a tooltip component itself the content the provider and the trigger component let's define the props here so value will be string on change will be value string or undefined and a void and placeholder will be a string optional and disabled will be an optional boolean so why am i using string as the value here well that's because this react currency input doesn't just mask things it also handles the decimal, the, well, what's the correct term for this? The numeric value, right? Remember that problem we talked about that JavaScript works with floats, so we don't have that precision? Well, in here, they handle that, right? And in JavaScript, that doesn't exist in any other way than a string, right? So that's why we have to work with strings here. So let's go ahead and let's just quickly define this component. So const amount input. Let's go ahead and attach the props here. Value on change placeholder and disabled. And now inside of here, all I want to do for now is just return a div hello. The reason I want to do that is because I want us to go back inside of the transaction form component features and render it so that we can see it here and so that we can see what we are developing. So let's go inside of the transaction form and let's go ahead and add our new amount input component from components amount input here. Right here and I want us to go and copy the payee and paste it just above notes. So next to last. So this is going to be the amount field. So let's go ahead and let's write amount here instead of input. It's going to use the amount input. And for now, what we're going to do is we can spread the fields like this and we can make the placeholder be 0.00 like this. And I'm going to refresh my local host here because I've shut down the server. And all I want to do is I want to see that component. So for now, all that should appear is a text, which will say amount input, I believe. Hello. Yes, there we go. So we have the amount label and we have hello as text. Perfect. So that is enough for us to start developing this. So first of all, let's go ahead and let's do the following. let's go ahead and let's parse the value so parsed value will be parse float value but we are only going to use it for this to detect whether something is income so if parsed value is larger than zero or if something is an expense so parsed value is less than zero so that's why we using parse float here, just so we can do these comparisons. Great. And now I want to do the following. I want to define a method, const on reverse value. So our users can easily change something from being an expense into an income or vice versa. So usually they would have to manually write the minus and they can still do that. But I want to give them a useful little button for that. So first of all, if value was not entered, no point in even doing the math here. Otherwise, we're going to do an on change here and we're going to pass in open parentheses parse float value times minus one to string. Like that. Or you can do const reversed new value. You can do that, for example, and then just do this. And then do new value to string. perhaps is whoops perhaps this is clearer there we go and now let's go ahead and let's start developing this so first of all we're going to have a div but the div is going to just have a relative class name so we can position elements inside and then let's add a tooltip provider here like that and let's go ahead and give it a tooltip child let's give it a delay duration here of 100 it so it opens a bit quicker. Let add a tooltip trigger component So let give it an as child option because inside is going to use a native HTML button element This will have a type of button on click here will be a very simple on reverse value, which we've just created. So the user can easily change something from an income to an expense. And now let's go ahead and give it some class names here. So class name is going to be our usable CN. By default, it's going to have BG slate 400. On hover, BG will be slate 500. It's going to have an absolute position so that we can position it how we want. So top will be 1.5, left will be 1.5. You're going to see why this will make sense when we add the input component, right? So let's add rounded medium, padding off two. So there we go. You can already see the little button right here, but it doesn't have anything inside. So padding off two, flex, items, center, and justify center, and transition. And then inside of here, we're going to do the following. If there is no parsed value at all, in that case, we're going to add an info like this, and we're going to give it a class name of size three. And besides size three, it's going to have a text white. Let's copy this two times. For the second one, we're going to use plus circle. And for the last one, we're going to use minus circle. So this one will not just check if there is no parsed value. Instead, it's going to check if is income. And the last one will check if is expense. like that. Perfect. So there we go. Now we have an info component here. And now outside of the trigger, we add a tooltip content, which will simply say use. And I'm going to specify a plus like this for income and minus for expenses like this. So there we go. Now when the user hovers, they know to use plus for income or minus for expenses, or they can simply click here. You're going to see how intuitive it is later. Let's go outside of the tooltip provider and let's add our currency input component right here. And now what we have to do is we have to style it. So you can see it's right here, but we have to style it a bit. So let's go ahead and do the following. Let's give it a prefix of a dollar sign. We can change this later, right? But let's keep it dollars for now. And now we're going to have to write a pretty big class name here, which is going to just basically match our input field. So what we can do is we can go inside of UI, components UI, input. So if you don't want to write it out, you can just copy the entire thing from here. So just these default class names here and just paste all of that here. Like that. There we go. So you can see how now it looks exactly as our input above, but we have a little problem here. So what we're going to add in this currency input before everything is a very simple padding left of 10. So we leave space for this. There we go. Perfect. So now we have the class name. Now let's add a placeholder here to be our placeholder. Let's go ahead and give it a value of value. Let's give it a decimals limit of 2. And let's give it a decimals scale of 2 as well. So I always want to work with two decimals. OnValueChange here is going to be onChange. And Disabled is going to be Disabled. And just below that, I'm going to add one more thing, just a very simple paragraph, which will check if isIncome. In that case, it's going to say this will count as income. Otherwise, if isExpense, it will say this will count as an expense. And we're going to give this a class name. text extra small and text muted foreground and margin top of two. Like that. Let's go ahead and try out our new component now. So there we go. This will count as income. But if I add a minus, this will count as an expense. And I can switch it from here, as you can see. Well, let's go ahead and add some color indication, just so it looks a little bit better. So inside of this button here, we define class name with the initial values and now let's add if is income we're going to add bg emerald 500 and let's go ahead and do the following let's add we can copy this and we can add on hover bg emerald 600 copy and paste this and let's do is income instead of emerald we're going to use rows like this. There we go. So now this will count as income. Actually, let's reverse the values. I think I did it incorrectly. This is expense. Yes, my apologies. There we go. So this will count as income. But if I click here, it says this will count as an expense. Perfect. So user can either write like this and then they can add their own minus or they can always switch from here and And if they have any doubts, right here, we have a little tip for them how this will count so they know exactly what's going on. Perfect. So I believe our transaction form is now ready to be submitted. So let's go ahead and try this out. Let's just confirm inside of the new transaction sheet right here that we are actually using the create mutation. It looks like we are. I think that there's nothing else we have to do here. So this is how we're going to try it out. So I'm going to go ahead and open my API transactions here. There we go. So localhost 3000 API transactions. And inside of here, I'm going to go ahead and try and create a new one. So I'm going to pick a date, 9th of May. I'm going to choose the cash account, the food category, or whatever you want. The payee is going to be payee. And I'm going to choose an amount of 500. And I'm going to write test here. and I'm going to click create an account and let's see looks like something is wrong here so we're going to have to debug uh oh we are not submitting anything yes so okay these are the values here account id amount category date great so this is fine but I forgot we have to do one change before the submit it's about the amount field so if you remember we made a decision on how we are going to store our amounts. And we ended up on this using integers of the smallest unit of the currency, or in our case, we're going to use milli units, which means we have to convert this to milli units. So this is an example of 10 and 15 cents. In our case, we have $500, right? So we're going to have to modify this using, well, we're going to create our own little reusable lid for that. So we can use it everywhere where we need to. And the reason I want to do this on the front end is, well, I looked at API specifications of existing applications which do this and their API and they do the same thing. And also because it's a good argument that it's the same way we parse dates, right? So we don't modify how the date is being sent on the backend. It just expects a specific format and it returns it in a specific format as well which is the iso string and then the front end has to take care of making it look good so it makes sense to use the same logic for the our custom amount situation here so what we're going to do is we're going to go ahead and we're going to create a method called convert amount to milli units so let's go inside of our lib folder instead of the utils here. And let me just go ahead and order these how I like it. So let's export function here. Convert amount function. Convert amount to milli units. It's going to accept an amount which has to be a number. And it's going to simply return math.round amount times a thousand. Like that. Let's also create the equivalent opposite, which will be convert amount from milli units, which will very simply get the amount and divide it by a thousand. So we're going to use, whoops, let me just close this. So we're going to use this to create the amount we can store in the database. And we're going to use this to show that amount to the user, right? Because user doesn't care about milli units. User needs to see that in dollars or cents or euros or whatever your currency is, right? So now that we have that, we can head back inside of our features, transactions, components, new transaction form, and we can head back inside of our handle submit method here. So what we're going to do is we're going to get const amount in milli units. convert amount to milliunits and pass in. Let's go ahead and define our amount to be parse float values dot amount and pass in the amount like this. So just confirm you have imported the convert amount to milliunits here. There we go. We have this and now we can safely submit that in here by doing the following, spread the values and pass in amount as amount in milli units. And there we go. We now have no type errors inside of here. Perfect. Yes, I think, yeah, the reason we needed the custom form schema was because of the amount. We needed to keep it as string here, right? That's the important thing. Great. Let's go ahead and try this out again now. So I'm going to go ahead and let's use our specific situation. Which was it? 10, 15. Let's say that's our income. 10, 15 here. Or maybe it would make more sense this to be an expense. And let's click create an account. Oh, it still says create account. So we have to change that. Okay, let's refresh this. And there we go. And you can see exactly how it's stored. Exactly how we want it. So we have the ID. We have the date. So 8th of May, food, category ID. So everything is here exactly as we expected it to be. Perfect. So before we wrap up the chapter, let's just fix this button. Do not say create account but instead let make it say create transaction right here And delete transaction We going to see this later in the next chapter when we enable editing of individual transactions So we just created a bunch of new components. So feel free to take a look in my GitHub source code if you are unsure. And see you in the next chapter. So now that we are able to create our transactions, let's go ahead and let's modify this page so that it actually loads transactions so that we can see more information. And finally, so that we enable editing the transactions. So let's go inside of the app folder inside of dashboard transactions. And let's go inside of page.tsx. So inside of here, we have a lot of instances of accounts because we copied this from our accounts page. So let's go ahead and do the most important one, use get accounts. So we are no longer going to need this import. So let's remove it. And instead, let's add use get transactions. Not transaction, but use get transactions. So multiple like this. So make sure you have an S at the end. And then we're going to use that here. So use get transactions. And this is no longer going to be accounts query, but instead it's going to be transactions query. So I'm going to go ahead and resolve these three errors, which I have right here. And I think I also have one down here. So it's loading accounts. And let's change that. So this is accounts. Let's change this to be transactions. Like this. Let's go ahead and add transactions right here. But we are still going to have an error here. And that is because of the columns. But let's before we go to the columns, let's also remove this. So we no longer need use bulk delete accounts. Instead, we need use bulk delete transactions. So let's add that here. And let's rename this to delete transactions. Let's fix the errors here. And let's add the mutation here. There we go. So we replaced all the hooks. I believe now if I search for account, there are no instances of the word account inside of this file. Great. What we have to do now is we have to resolve our columns. So let's go inside of columns right here. First of all, let's fix the response type. So we're no longer looking into the accounts here. Instead, we are looking into transactions. And I believe that get 200 data and this is exactly the same. There we go. So this is our response type. ID, date, category, category, the payee, amount, notes, account ID and account. Great. And now let's go ahead and let's modify this. So we have these accessor keys for name. So we're going to change this instead of name. The first one is going to be date right here. And the header will say date like this. And let's go ahead and let's modify the cell. Right. So I'm going to add cell here. I'm going to let me show you why I'm doing that. Right. So right now, if I just save columns, as you can see, there is no there are no longer errors here because the columns now match. But let me just show you that now if you have an existing transactions, this is how the date will look like. Right. So go ahead and create a transaction. So let me pick, for example, 9th of May. Let me pick something here. Test, test. Let's create a transaction. There we go. So you can see how this looks, right? So let's go ahead and resolve this. So we're going to go ahead and go inside of columns right here. And we're going to add after our header here, add a cell property. Go ahead and destructure row. and return a method, which will very simply return a span element here. And now what we are going to do is we're going to write const date row get value. Whoops, let me scroll up so you can see. Get value date as date, like this. And inside of here, we're going to use our format method from date FNS. So just make sure you've added this. and we are going to format it by passing in the date, and then we're going to use a specific format. You can use anything you want, of course. I'm going to go ahead and use this format because I like it the most, 9th of May 2024. There we go. Let's just confirm that this date picker is working. So if I choose 12th of May and choose some random stuff here, let's just see it was created but why is it not here all right we might have a little bug here but we're going to come back to this later okay we're going to debug that but let's first just finish our columns so we don't get sidetracked all right so we just finished the date and now what I want to do is I want to copy and paste this entire thing so with the accessor key header and sell. Let's copy and paste it. And this one will go to category. So this will be category like that. And now instead of here, well, for now, what we can do is we can just say row dot original dot category like this. There we go. So this will just save food. So for now, let's keep it like this. Let's keep it simple. Now let's go ahead and copy and paste this again. right here. So just above actions, we're going to call this payee and this will be payee. And instead of here, we don't need sell at all because it's just going to be plain text, right? We don't care. So for the one above, we're going to change this later. That's where I rendered cell in a custom way. So let's go ahead now and let's copy our existing category. So copy category because it has the cell option, which is what we'll need for the next one. So skip the and now let's add the amount. So this will say amount. Let's go ahead and let's get our amount using parse float row get value amount like this. And then inside of here, let's go ahead and let's write format currency, which is something we don't yet have. So we're going to have to go inside of our utils and we're going to create the format currency method. So let's do that. I'm going to go inside of lib folder, utils, and let's export function format currency. So this is going to be for the frontend only. It's going to accept a value, which is a number. And inside of here, what we are going to do is we are going to return a native JavaScript API for formatting. So intl.numberformat and dash us and then open options. So style is going to be currency. Currency will be US dollars. Minimum fraction digits will be two. And then we're going to attach dot format and pass in the value. There we go. Now go back inside of columns and you can import format currency from live utils and pass in the amount. So just ensure that you've added this import right here. Let's check this out. and there we go. So you can see how this looks now and you probably also see a little bug here. So this is definitely a larger amount than what I've entered, but don't worry, we are going to resolve this later because remember, we are working with that milliunits value, right? So we have to take care of that on the front end, but we're gonna do it later. All right, so we have this and now what I wanna do is I wanna use the badge element instead. So let's go ahead and do the following. Let's go inside of the terminal and let's do Bunex chat CNUI at latest ad batch like this. Let's do Bune run dev again. Let's refresh our local host. And inside of here, instead of using span, we're going to be using that new batch component, which we've just installed, right? So just make sure you've added this. All right. And now we're going to go ahead and give it some properties. So variant, if amount is larger than zero, in that case, it's going to be destructive. Otherwise, let's go ahead and let's make it a default. And let's give it a class name of text extra small font medium, px 3.5 and py 2.5 like this. So let's see if this is going to improve anything. All right, so it kind of looks better, but we're going to go ahead and we're going to modify these variants now. So let's go inside of our components folder UI and go inside of badge directly. So I want to add another variant here. So right here after outline, I'm going to add primary to be border transparent. bg blue 500 slash 10 and text blue 500 and then inside of my columns inside of transactions right so where we just were adding the badge instead of using default i'm going to be using primary like this so let's see how that looks like there we go so this is how i want it to look like for positive income and for negative income i want it to look red but not that red. So I'm going to go and find the destructive variant, which is what we're using here, right? So find destructive. And I'm going to change this to be BG destructive slash 10. And I'm going to go ahead and remove the hover like this. And I'm going to leave the text to just be destructive as well. So text destructive and BG destructive slash 10 and border transparent. So that's how I'm gonna modify the batch component. And there we go. This looks like something now. Great. So let's go ahead and see what else do we have\nSo now we have to add the last field here, which is going to be the account. So go ahead and find category and copy it because it's the most similar one. So just above the actions, go ahead and add account. And this will say account. And inside of here, row.original.account, we are going to modify both the account and the category later. And there we go. So we now have everything we need here to display the proper transactions. And now we have to discuss this amount option. So what's going on? This is in million units, right? So this is, I believe, maybe $10.50, but we store it as $1,500, I think, right? So that's an issue, right? We store it in million units. So you have to find one place where you would convert those million units into, well, dollars, right? So you can choose how you want to do it. If you are certain that you are always going to do, always going to wrap currency inside of this format currency, what you can do is you can convert this value, for example, const final value, into convert amount from mill units and pass in value here and then add final value here instead. So just import convert amount from mill. Actually, you don't have to import. It's right here. And if you try it now, there we go. So now it looks normal, right? So that's one option, you know, but I kind of don't like this. I don't know why. I don't want to depend on this because sometimes I want to use some different formatting value maybe. So let's bring this back to just use value. And instead, I'm going to show you what I did in my code. So what I did originally, you can choose which option you like. What I did was I went into features, transactions, API, use get transactions. Inside of here, I modified the data. So this is what I did. I returned data.map. I got the transaction inside of here. and I spread the transaction, and I only modify the amount by adding convert amount from millie units, so make sure you add this import right here, and I simply pass the transaction.amount instead, so that is what I did, and now I don't care about my formatting function, right, let me just refresh to see if this is still working, there we go, so you can see how now it's working just fine because this is kind of closer to the backend, let's say in some way, right? This is right here in InUseQuery. You can, of course, find many different ways to transform data with React query. There's also something called select. It's not here, it's outside of. So here you can add select, for example, you would get the data. So you could do that here if you want to. They have a couple of blog posts about converting data, right? I think this is completely fine for our case, right? You can also play around with memorization, or you can do the formatting thing, right? Whatever you like. But I think for the tutorial, this is more than enough, and it kind of gives me confidence that my data will behave exactly how I want it, regardless of where I use this hook, because I know my data will be normalized here. Great. So we resolved that. Now let's go ahead and Let's see what's going on with our little bug there. So I think if I'm going to keep my terminal open to see what's going on, I'm going to create a new transaction. And I think I picked 12th of May. I picked an account. I wrote something. I added something and I clicked create a transaction. And transaction seems to have been created right here, but it's not loaded here. And if I go into API transactions, I also only have two. All right. So something is wrong. So I'm going to go ahead and debug that in the next chapter for now. I think it's enough for us to see these transactions right here. All right, so I realized what is the bug. No need to debug it in the next chapter. So this is actually quite important to mention in case you're encountering this. There is no bug. Nothing is happening. So the records actually do exist. But if you remember, inside of our use get transactions here, we have this from and to params and we are passing empty ones at the moment. So if you remember our API for transactions inside of the global get right here, let me close the other ones. instead of the first get one. If we don't have from and to, we default to the last 30 days, which means if you create a transaction in the future, it's not going to be displayed here. So go ahead and try and create a transaction in the last 30 days. So for me, it's the 9th of May. So if I choose the 1st of May, it's not gonna have any problems. So let me show you this test and let's use this much test, create a transaction. And there we go. It's right here. Great. So we can continue developing on this. I thought that we're going to have to debug something, but we don't. Great. So what I want to do now is I want to modify this category and this account field so that we can easily switch it, right? I want to click here, for example. And also if something is uncategorized, I want to display uncategorized option here. So let's go ahead inside of our columns for transactions where we just added the account, the last one, right? And let's go ahead and do the account column first. So inside of the transactions here, we're going to create a new file account-column.bsx like this. And let's go ahead and do the following. Let's import use open transaction, use open account, my apologies, right here. and let's go ahead and import cn from at slash lib slash utils. I think we're going to need this. Let's write type props here and let's make it an id of string, account of string or null and account id of string or null as well. Let's export const account column and let's make this ID account and account ID. And let's just assign the props here. And now inside of here, I want to create a div, which will very simply either say account or, well, it has to have an account actually. There is almost no chance that the account doesn't exist. right so we can do this right account always has to exist and this will be account id and let me not misspell account there we go so account always has to exist right we don't have to worry about that here so i'm gonna go ahead and give this a class name of flex item center cursor pointer and hover underline like this. And now what we can do is we can also import, we can use this use open account here. So let's do that. Const on open, on open account. Let's give it an alias, right? So it's more readable. So const on click, very simply, it's going to call on open account with the account ID. So we are reusing that hook to edit any account. So we can add this on click here. There we go. And now we can go back inside of our columns. We can find the account here, find the cell. And instead, what we're going to do is we're going to use that account column from dot slash account column. So just make sure you've added an import for the account column here. And pass in the ID to be row.original.id account row.original.account and account ID row.original.account ID like this. And let's see if we are missing anything inside of here. We actually don't need the ID property. So we can remove this and we can remove the CN import. and now we should get an error here but my TypeScript is slow today. Oops. So I should get an error here. There we go. So remove that. And now if I refresh here, when I hover, there is an underline and I should be opening this but it looks like it's not opening. So let's see. Is there any error being thrown? There is not. All right. So let's see what's going on. Row original account ID. So I'm going to go ahead and do the following. I'm going to console log the account ID and see if that changes anything. So let me refresh this and let me keep my console open. All right, so we do have account ID, but it is not opening. Let's see accounts. So this is opening just fine. All right, so something is a bit off, it seems. All right, so this is the issue. It is very, very unusual. So I did some changes here because I could not understand why the account was not opening. Well, it's our fault. More specifically, it's my fault. I didn't look at the import. We are importing from transactions hooks because we copied the entire features from accounts, including the hooks. We need this hook, but I am importing this one. Use open account. so we don't need that, we need the one from accounts my apologies, I was losing my mind over why this is not working but let try it out now so if I refresh there we go and I can rename this to cash too and okay so now we have to go back I believe inside of our features accounts API use edit account. There we go. Invalidate summary and transactions. So let's now add transactions inside. So if we try this again now, let me pick an account here. So cache 23, save changes. There we go. So we can now easily modify an account if we want to. And now let's do the same thing for the category. So I'm going to go inside of my app folder, dashboard transactions, and I'm going to copy and paste the account column. And I'm going to call this category column. Inside of here, we're going to have category and we're going to have category ID. but the thing with this is that it can be undefined. So we're gonna add or null here and we are also gonna have the ID here which is something we didn't have in the last one. So inside of here, we're gonna have an ID, we're gonna have a category and we're gonna have a category ID like this and instead of use open account, this time we're gonna have an import, use open trend category from features categories hooks use open category let's edit here let's alias it to on open category and we're gonna pass in the category ID inside but only if we have category ID like this and inside of here we are going to write category or uncategorized. And we are also going to say if we don't have category, in that case, we're going to render a triangle alert icon from Lucid React. So we kind of warn the user. Let's give this a class name of MR2, size 4 and shrink 0. and let's also add the import for cn from lib utils let me just separate this from the features above and let's rename this to category column of course so i'm going to go ahead and turn these into hard code from hard-coded classes into dynamic so this will be the default ones And then I'm going to add if we don't have transaction, sorry, if I don't have category, in that case, it's going to be text rows 500 like this. There we go. Let's go inside of columns. And the same way we used account column, let's find the category. There we go. So find the accessor key category, find the cell and change this to use this. but instead it's going to be category column. So import category column just below the account column here. This is going to be category and this will be category ID. And we are also missing the ID, which will be row.original.id. You're going to see why we need the ID in a moment. So and now let's also prepare ourselves by going inside of features, categories, API, use, edit, category. and go ahead and add invalidate transactions here. So we can remove this part. And we can also do it in use delete category. So we can do that here as well. Like that. And we can do the same thing in accounts, delete account. Same thing, transactions. All right. and now let's go ahead and check this out. So if I refresh here, and if I select food, it opens food. If I rename it, there we go. All categories are now renamed. Perfect, but we still need to handle the case if category doesn't exist, and we're gonna go ahead and do that next. So we can't yet do it because we are missing a hook, and the hook we are missing is features, transactions, hooks, this one. So right now we have use open account. Let's create use open transaction finally. So this was the hook that caused that problem because it's named exactly the same because we copied it. So open transaction state here and everything else can stay exactly the same. And now we're going to go inside of this components and we are going to modify the edit account sheet to be edit transaction sheet. There we go. Let's go ahead and let's rename it. So edit transaction sheet and let's immediately go inside of providers, sheet provider, and let's add it here. So import edit transaction like that and let's add it right here. so we don't forget that. Great, and now inside of here, we are not gonna use use open account, so we can remove that. Instead, let's add import use open transaction from dot dot slash hooks or features transactions like this. So we're basically gonna have to replace all instances of anything account-based like this. So let's go and remove use get account in favor of use get transaction. So I'm going to change this to features, transactions like this. Let's find where we use use get account, we use it right here. Let's modify this to be transaction query. And let's use it right here. Let's see if we're using it anywhere else. And we are using it right here. So let's go ahead and modify that. So if we have transaction query dot data. In that case, we're going to have account ID be transaction query data account ID. And then we're going to have category ID. Then we're going to have amount, but we're going to do amount dot to string like this. Then we're going to have date. So we're going to check if we have the date. In that case, we're going to parse that as an actual date like this. So let me go ahead and collapse this. Otherwise, it's just going to be your basic new date. Next one is going to be payee. So that's going to be transaction query data payee. And last one is going to be notes like this. So let's go ahead and just make this all empty. So account ID, empty. Category ID. Amount. Date. Payee and notes. Like this. There we go. So those are our default values here. Let's see what else we have. so we have use edit account let's remove use edit account in favor of use edit transaction from features transactions api so let's see where that is there we go use edit the transaction we don't have to modify the naming here that's fine we have used the lead account let's remove that as well in favor of use delete transaction like this from features transactions like that. And let's use it here. The naming is fine as well. Great. Now we have to modify the form values. So we no longer need insert account schema. Instead, we need insert transaction schema. So we're going to modify this to be transaction schema.omit ID true. So not pick, but omit, right? There we go. So that should resolve a couple of errors here. Now let's modify the naming here. So it's not going to be edit account, but instead edit transaction. Inside of here, it's going to say edit an existing transaction. There we go. we are no longer going to be using the account form. Instead, we're going to be using the transaction form from dot slash transaction form, or you can rename it to features transactions components. Whoops. Transaction form. And let me just move it with these ones and I can now remove account form. If I search for account, there we go. I only have this instance. it looks like. Okay, four others. So first, let's go ahead and do this. You are about to delete this transaction. Let's see where else do we have this account ID. It's fine. Great. This is fine. Perfect. So now we have to resolve this. So the default values seems to have some issues. So we'll resolve that in a moment. What we have to do first is we have to also add a key here, ID, so it destroys and re-renders this form on every chain. Actually, we might not need that. I think that was from my previous exercise that I did. All right, so this is what we have to do now. We have to go inside of our new transaction sheet and we have to do this. Remember, we need the category and the account options. and let's go ahead and copy all of that and let's add it to the code right here. So I'm going to do it just after our transaction hooks. We add all of these here and we can actually copy the imports. It might be easier. They are right here. So accounts and categories or you can just manually add these. So let me separate these from transactions. Let me save. And there we go. I no longer have any of these errors right here. Now let me extend my is pending. So besides this, edit and delete, we are also going to have the transaction query is loading. We are also going to have category mutation is pending. And we are also going to have account mutation is pending And for the isLoading we are going to have transaction query isLoading category query isLoading, or account query isLoading. So a bunch of options like this. There we go. So now let's go ahead and let's add all of those things to the transaction form, or we can just copy from here. The props should be exactly the same. And don't forget to bring back ID because I just removed it. There we go. So make sure you have ID inside of here. There we go. But we also need to pass in the default values, right? So let's add default values to be default values right here. And this is the place where we are getting some errors. So the property date is not assignable, it seems. So let me see if it's maybe because of this. What if I just mark this as undefined? Okay, so it's because of that. Okay, so can I just write new date here? Okay, that seems to solve it. So it just must not be a string. All right, there we go. So I think that we are ready to try this out. Of course, I have these errors now. This is, again, TypeScript losing its mind. Okay, it's good now. So this was a very big component. You can, of course, copy it from my GitHub if you're unsure. And we are not using onDelete anywhere. So let's go ahead and do that. So we have on submit, but we don't have on delete. Because this is an edit method. There we go, on delete. Let's try this out now. Before we can try it out, we have to go inside of app, dashboard, transactions. So inside of columns here, but more specifically, we need our actions. So the last one right here. So this is the last place to replace the account instances. So let's import use open transaction here. Let's see where we are using it. We are using it right here. It should be throwing an error. There we go. It's throwing an error. Now it's no longer throwing an error. And the last one, use delete account, replace that with use delete transaction. There we go. Let's add that here. and everything else should be exactly the same. And let's just change this to say you are about to delete this transaction. There we go. I think that should be it. So now if I go ahead and click edit, there we go. I can now edit this. Perfect. So that's exactly what I wanted to do. Great. So let's go ahead and try something else now. So now I'm going to go ahead and create a new one. So make sure it's in the last 30 days. But this time I'm not going to select a category. There we go. So now it says uncategorized. So now when I try and open this, nothing happens. So what I want to happen is that when I click on something uncategorized, I want the edit to open. So we're going to go back inside of the columns, find the category column. So that's why we are passing in the ID. so that inside of here, I can add an open on open transaction, use open transaction like this, right? So I want another feature here, use open transaction. And then else, if we don't have, we're going to use on open transaction here with the ID from the props, right? Because we are passing it. So now if I finally refresh And if I click on categorize, there we go. I can now select the category. I can save and it is resolved. Perfect. Let's go ahead and let's try individual delete from actions. Confirm. There we go. Let's try and open this and delete from here. There we go. And let's select these three and click delete. There we go. Works perfectly. And now what I want to do is since we copied the entire features thing from accounts. Let's go ahead and right click. Let's click find in folder and let's search for account. All right. So account ID is fine. That's completely fine, right? Use get accounts. That's fine. Use create account. All of those things are fine. So I'm looking for something that might tell me that I forgot to rename something, but all of these are just options to fill the account field. So that's completely fine. Now I just want to check inside of my app folder, dashboard, transactions. So I'm gonna right click, find in folder, again, account. So account column, that's completely fine. Columns itself uses the account column. That's completely fine as well. So nothing strange here. Perfect. So we now have a working transaction history. But one thing I think we forgot to do. So let me again, try something in the last 30 days. And I'm going to call this, for example, search. And let's give this a price. So if I try and search this, nothing happens. I cannot filter here. That's because we forgot inside of our app folder, dashboard transactions page, we forgot to add a proper filter key. So let's make that payee like this. So now if I write search, it's this one. If I write something else, it does not exist. Amazing, amazing job. You've wrapped up the transactions page. Well, not exactly because in the next thing, what we have to do is we have to enable file upload via CSV. So that's what we're going to do in the next chapter. We are going to use that bulk create endpoint. Great, great job. So now that we've wrapped up the data table for transactions, I want to add another button here, which is going to be used to upload a CSV file. So let's go ahead and do the following. Let's go inside of our transactions page.psx right here. And let's create an enum here at the top. So I'm going to create an enum variance. First variant is going to be a list representing the current state that we see right now, the list of transactions. And then we're going to have an import variant like that. And let's also create a constant initial import results. And that's going to be an object which has data, which is an empty array, errors, which is an empty array as well. and we're also going to have meta, which is an object. This is going to be used for a package that we are going to add, right? So I'm just preparing that here. Now, inside of the transactions page, let's go ahead and create a use state from a React here. So just ensure that you've added use state from a React like this. And now inside of here, let's go ahead and let's add a variant, set variant here. and let's give it a default of variance.list and you can go even further and define the exact type to be variance inside. There we go. So now what I want to do is I want to find, I want to go after this if clause right here before the return and if variant is variance import. In that case, we're going to return a fragment. And for now, we're just going to write a div saying this is a screen for import. So we are going to render a completely different thing if the user selects the import variant. So if I refresh my app, everything should be exactly as it is now. But if I change this to import, this should change right here. There we go. this is a screen for import. So that's what we want. So let's bring this back to be the list for now. So now let's create a button, which is going to go alongside this add new button. So I'm going to call this upload button right here. And we're going to have a property on upload. We're now just an empty method like this. And we actually don't have to collapse this like that. So let's go inside of the transactions here and let's create our upload button dot the assets like this. And from here, I want to go ahead and I want to install a package. So we're going to install npm install or bun add react papa parse. So that's the package that we are going to use. Let's go ahead and do bun run dev again. Let's import the upload icon from Lucid React. And let's import from React Papa parse. Use CSV reader. And let's import a button component from components UI button. Let's write the props on upload. It's going to give us the results, which can be any and a void. And let's export const upload button here. assign the props, and let's destructure the on upload here. Now inside of here, we're going to go ahead and do the following. So we are going to destructure, whoops, not here, but outside of the return, we are going to destructure CSV reader. Again, my apologies, CSV reader from use CSV reader like this. and then in here I'm going to add a comment to do add a paywall so later when we implement subscriptions and payments we're going to add a paywall here but not for now so for now what we're going to do is we're going to use this csv reader which we've just exported from here and inside of here we going to open a method we going to open another method and the structure immediately get root props inside like that and give it a type of any Go ahead and immediately return a button component Let go ahead and give it some props So it's going to have a size of small. It's going to have a class name of full width, button LG auto, and it's going to have get root props executed like this. Inside, we're going to use the upload icon with the class name size4 and MR of 2, and we are going to write import like this. So now let's go back in page.dsx, and let's add the upload button, export const upload button. like this. So upload button from dot slash upload button. There we go. So if I refresh this, I think that we should see our new button here next to add new transaction. Let's see. There we go. So right here, let me expand. All right. But we do, it looks like we have to add some modifications so that they are together and not so far apart. So let me just zoom out so I can see what I'm doing here. And let's go back inside of the page here. So let's find where we have this. So this is here in the card header. So what we're going to do is we're going to wrap these two buttons in a div together like this. And we're going to give this a class name, flex item center and gap X of two. There we go. So now this is looking better. So what you have to obtain next is a CSV statement of a bank account. So I have an example here, which you will be able to find in my source code. So I extracted these from Revolut. So if you're using that, you can go ahead and extract your statements here. So you can also pause the screen if you want to write your own. So what's important here? The most important thing is the dates and how they are written. So if you want to write this yourselves, you can pause the screen and write it. You don't need all of these, right? It will work even with just one, right? Just make sure you have at least two lines here. Make sure that the currency has the same, sorry, that's the amount. So make sure that the amount has the same format and make sure that the dates have the same format because we are going to work with these formats right here. So you can find this file in my source code. If not, you can write one yourself. And now what we have to do is we have to import that file. So right now, if you go ahead and click import, You would choose that and you, of course, save that as a .csv file and you would select that. So now we have to change the screen when the user uploads a file. So let's go ahead and do this. Let's go ahead and let's actually use this upload, which we are not using anywhere. So I'm going to go ahead and do the following. On the CSV reader, we are going to add on upload accepted and we're going to pass on upload. And then inside of here, we have the onUpload, and we can use that to change what we need, right? So let's go ahead and define our onUpload here. OnUpload is going to accept the results, which will be a type of initial import results. So they are going to look like this. You can find more about that in the documentation of React pop-up parse. So inside of here, the first thing we are going to do is we're going to set the variant to variants.import. That's the first thing we are going to do. And now let's use this on upload and let's assign it here on the button. So this on upload will be fired when upload is accepted on the CSV reader. So let's try it out. So now once I upload, this should change. There we go. this is a screen for import so that's what we are going to create next but first of all let's create another state here for import results so just below this I'm going to write import results set import results use state initial import results so we are going to use this as our default value let me show you how this looks like in one line there we go so we're going to go ahead and set the import results here to the new results like that. And now we are able to work with that. So first of all, let's also create a on cancel import here. So that is very simply going to set import results back to initial import results. and it's going to set the variant to variants.list in case we want to cancel the import. Great. And now what I want to do here is instead of rendering a variants.import, we are going to render the import card. Now, import card does not exist yet. So let's go ahead and create it. So inside of the transactions folder here, Let's create import-card.tsx. And let's go ahead and export const import card. Return a div import card. And let's go ahead and import it here. Dot slash import card. and now there should be no errors and you should see the import card here. Great. Let's see if we have to give it some props first. So inside of the import card, I'm going to go ahead and create a type props to have data, which will be a string, a metrics of strings basically. And on cancel will be a very simple void. And on submit will accept that data, which will be any and return a void. So unfortunately, the React popup parse package is not strictly typed. So it's just easier to use any with this, but you're going to see what kind of data that's going to be. And now let's assign these props here. And let's use the data on cancel and on submit here. like this. Great. And now we can go back inside of the page here and we can assign all of those things. So data will be import results dot data on cancel will be on cancel import and on submit for now will be just an empty method. So if you're wondering how do I know this is data? Well, well let's try it out so on upload let's console log the results oops all right let's see the results when we upload so I'm going to expand this and I'm going to import a statement and there we go results data and this is how it looks like so it's a matrix of strings right let's go ahead and continue developing the import card right here So now when it has all the necessary elements, let's go ahead and just define a couple of more constants here. So const date format, which we accept, is going to be yyy-mm-dd-hh-mm-ss. That's going to be the date format we support. And the output format, which our database accepts, is this. Like that. So we define that here immediately. so we know what we are working with. So this is what needs to appear in your CSV files. Great. And now let's also add required options. So that's going to be the amount field, the date field, and the payee. So these three things are required to make a transaction. Great. And I'm also going to go ahead and just prepare one interface here. Selected columns state. So that's going to be key string, which will be string or null, like this. And without an equals assign. So very generic. You're going to see why in a second. All right. So what we have to do now is we have to import everything we need from card. So we can do that here. So how about we just quickly add that, everything we need from card. and now what we can do and let's also add the button from components button what we can do is we can just copy from our main page this div and all the way up to the card content right so we can copy this part and add it in our import card here like this so now obviously we have a couple of errors here so first of all card content will just say hello and then we will close card content and then we will close card and then we will close the div we are not going to use the upload button and we don't need the plus the on click can just be an empty arrow method and the this will actually say cancel like this and this will not say transaction history but import transaction there we go so you can see how this looks now like that. And now let's go ahead and enable the cancel button. So I'm going to go ahead and use the on cancel and use it here. So now when you click on cancel, there we go. It resets the entire thing. So if you import this VSA file, it will change this to import transaction like that. Great. And now what I want to do is I want to create something called an import table, right? But just before we can do that, we have to do something else. So we have to use this data and we have to separate the headers from the body. So if you took a look in the console log, right? You know that the first item in the array, let's take a look at it again. So you know the first item in the array in data, whoops, it's not here. Am I still adding the log? Let me just see.\ninside of page here. Whoops. Inside of page, when I upload something, I'm still logging. All right. So let me add the import. Inside of data, as you can see, the first array is the header, the type, product, right? These things. And everything else is the content. So that's what we need to separate in the import card here. So inside of here, I'm going to add const headers to be data, the first in the array and then body will be data slice one like that. So now I know what's the headers and I know what is the body. And in here, I'm going to prepare a use state from React. So just make sure you've added this. Let me move it to the top here. So use state inside of here will be an empty object, but we're already going to prepare it to use the selected columns state. and by default is going to have the selected columns and set selected columns like that. All right. And now let's go ahead inside of the card content and let's render import table, which again does not exist yet, but we are going to create it. So it's going to accept headers. It's going to accept body. It's going to accept selected columns, selected columns and it's going to select on table head select change which for now can just be an empty arrow function so now we have to go ahead and create the import table component we're going to do that in the transactions folder as well so import table dot tsx and we have to import everything we need from components ui table so let's go ahead and import table, table body, table cell, table head, table header, and table row like this. And let's define the props here. So we accept the headers, which are array of strings, and the body, which will be a matrix, right? So let's go ahead and define selected columns here to be a record. string any or it's going to be string or null. My apologies. This is the correct type. And then we're going to have on table head select change. So I know you're wondering what this is, but you're going to see in a minute. This is quite complex to explain, but basically we're going to create a type of table which will be able to use these, like these things at the top, the headers, but instead they're going to be select options. So you're going to be able to change these to not be category, but you would be able to click here and then select that to be payee, for example, right? We need to match the results from the uploaded file into what we expect here. So basically this method is going to be this value, so column index, which is a number, value, which is a string or null, and a void. So that's going to be that, okay. So those are our props. Let's export const import table here. And let's go ahead and assign the props. whoops like this let's destructure all the props so headers body selected columns and on table head select change and now inside of here we are going to return let's go ahead and scroll this a div with a class name of rounded, md, and border, and overflow hidden. And inside, we're going to add a table, a table row. We're going to add a table header first, and then we're going to wrap this table row inside. We are going to give this table header a class name of bg muted. and the table row itself will iterate over the headers dot map and inside of here we can skip the item and we can just use the index go ahead and open that and for now you can add just the table head here and render the index inside and use the key as index as well for now and then inside of here outside of the header we're going to add a table body and the table body will iterate over body.map. So it will get row, which is an array of strings and an index. And inside of here, we're going to use a table row with a key of index. And inside, we're going to iterate over the individual row.map. So we're going to get the cell and index and we're going to render the table cell, finally. So let's go ahead and give it a key of index, render the cell and close the table cell. So quite complicated, I know, but I don't think there is an easier way to handle converting a CSV file into something we can accept into our database and allowing the user on the front end to modify that. All right, so let's import the import table from .slash import table like this. and we pass in the headers and the body. So I think we should be good to at least see some results here. So if I go ahead and import this statement, there we go. So this is what I wanted to load. You can see how now I can load my exact information here. And now we're going to turn this indexes into component, which will be able to select and choose which of the required fields should we map the data for? So we will choose this as the date. We will choose this as the payee, this as the amount. So that's what the goal is with this. Great. So let's go ahead and develop that next. So again, please just use a proper CSV file. The date needs to be in this format right here. I suggest you simply use the one I have in my repo or the one I have in my repo is from Revolut. So if you have Revolut, just go ahead and create the statement export from there. And one important thing, right? So after we add these, keep in mind that most of them are going to be in the past, right? So maybe they won't be in the last 30 days. So perhaps when we upload them, you're actually not going to be able to see them right here in the transactions, right? So don't get scared if that happens. It's because of our date filter, which only goes back 30 days, right? So don't get scared that something is wrong. That's what we are going to confirm in the database as well. So let's go back inside of the import table here. And now we have to fix this. We shouldn't just render the index. Instead, what we should do is we should handle table head select here. It should be a self-closing tag and it's a custom component we are going to create. So column index is going to be the index. and selected columns are going to be the existing selected columns. And on change will be on table head select change like this. And now in order to develop that component, we have to do Bunex chat CNUI at latest. Add select components. So we already have react select, but we don't have a plain old select component. So that's what we just added inside of our components. Now you should see in the UI folder, a select component added. Great. So now let's go ahead and let's go inside of the transactions folder and let's create not a folder, but a file table head select dot the SX right here. Let's go ahead and let's define the props for it here. Column index, which is a number selected columns. which is a record string string or null and we're going to have on change which is going to be make sure you use columns here so the first parameter will be the column index which is a number and the second is the value which can be string or null so it needs to be null because you need to give the user an option to skip specific fields, right? Now let's define all options here again. Amount, A, notes, and date. Basically just ensure that they match where did we write our in the import card perhaps. Okay, so these are the required options and these are the options that we can use, right? So just make sure that there are no typos with the required options here. So amount, date, payee. Payee is with two E's. So make sure you don't misspell any of those. And in here we have the extra notes, right? Because they are not required, but users should be able to select that if they want to. And now let's go ahead and let's export const table head select. And let's go ahead and return this. Let's assign the props. Let's destructure everything we need. So column index, selected columns, and on change here. Great. And let's return a div select. Now let's go back inside of the import table component where we have this error. And let's import that from dot slash table select. So now when I import here, all of them should say select, right? They're all now using that same component. Great. So let's get inside of the table, head select, and let's develop that component. So first of all, we're going to need to add an import to everything from add slash components UI select. That's the first thing. So that is the select, select content, select item, select the trigger, and select value here. And we are also going to need to import the CN library from lib utils And now let go ahead inside of the method And let first of all define the current selection if it exists So selected columns. And inside of here, we're going to use backticks column underscore column index. So that's how we are going to target our selected columns here. Let's go ahead and change this to be the select component. let's give it a value of current selection or an empty string. So this should be current selection, not current select like that. So that's how we're going to track what's currently in this header, right? We have to specify it like this because there is no other non-generic way to do that, right? And this is how we're going to track it in the onSubmit method by looking on the indexes. And then we are simply going to map everything under that index as whatever we select in this component. So let's do on value change here to accept the value and passing on change with the column index as the first argument and the value as the second argument. Now inside of here, we are going to add the select trigger component and let's give it a class name, cn. so we're going to give it a focus ring offset zero focus ring transparent outline none border none bg transparent and capitalize and we're going to change if we have a current selection i want to indicate to the user that they already selected for this column by giving it a blue color and inside we're going to add a select value with a placeholder of skip right if they don't want to select anything they can always skip and now let's add a select content here and let's add a first select item inside which will have a value of skip by default and a label of skip inside like that so that's going to be the hard-coded option and now we're going to either it or the options ourselves. So options.map, we get the option and the index. And inside of here, we are rendering a dynamic select item component. So first of all, inside of it, we are going to render the option, right? So do we have the options at all? Let me just see. Where do we... Oh, we have the options right here. My apologies. I thought that they were a prop. They are not. So we have the constant options here. All right. I confused myself for a second here. So we have the options. And now inside of here, we can pass in the key to be index. We can pass in the value to be option. We can pass in disabled to be disabled. We don't have disabled yet, but we're going to create it here in a second. And let's go ahead and give it a class name of capitalize. capitalize. So when should an option be disabled? The option should be disabled if it's already used by another column. So if I select that this row is a category, I should not be able to select it here again. It should be disabled to show the user they already chose that option. So I'm going to modify this a bit. I'm not going to immediately return here. Instead, I'm going to open this curly brackets and I'm going to end a curly bracket here. And then inside of here, I'm going to add a manual return. There we go, like this. So this will resolve the errors. What that allows me to do is go here and define the disabled constant. So to see if something is disabled, we are going to go over object.values of our selected columns. And then we can look at .includes option. If it does include the option, we're going to do end, selected columns. We have to find our matching column using the column index we're going to check if that is not equal to the option like this so quite a comparison here but it's the only way we can ensure that we already selected that in another select table head select component right all right let's see if this is working I think this is it. It can now render this. Great. We already added it here. So let's try it out. So if I go and import this and select this, there we go. So everything is skipped by default. So I should now be able to select date here. All right. It's not working and it's not working because we didn't implement the function that will actually select this. So let's go ahead and do that next. So for that, we have to go back inside of our import card component right here, because we passed an empty function for on table head select change. We have to create a method which will manipulate the selected columns here based on the information sent from the table head select component. So let's go inside of the import card right here. And just below our body, let's define const on table head select change. and in this method the first argument will be the column index which will be a number and then we're going to have a value which can be string or null and inside of here we are going to modify the set selected columns right here by getting the previous value of them and then we're going to open a method right so don't return an immediate object just open a method here and we're going to define the new selected columns inside. We're just going to spread the previous ones, right, or the current, however you want to call them. And then we're going to do for const key in new selected columns. We're going to check if new selected columns key, so the existing value matches the new value, in that case, new selected columns key will be reset to null, like this. And then inside of here, we're going to say if value is skip, in that case, the value will be null for this next step, which is going to be new selected columns. And then we're going to write Column underscore column index will be value like that. And then return new selected columns like that. So just be careful with this names. So this repeated a couple of times in our project. So make sure that it is exactly the same every single time. All right. So it should be here in the current selection and in here in the disabled in the tablehead select. Great. So now inside of the import card, we can use the on tablehead select change and we can pass it here. There should be no type errors because the types are correct. Let's try it out again. So if I select date here, there we go. This becomes date. And you can see how it's disabled in all the other options. So now this will be my payee and this will be my amount. And I don't have to fill the notes, but if I want to, I can put the notes here, for example. And I can always bring this back to skip. And then you can see amount is now freed. Or if I skip this, then payee is freed. If I skip this, then date is freed. So that's what we wanted to achieve. Great. So now what we want to do is we want to create the continue button, which will indicate to the user how many of the required fields have they set. Let's go back inside of our import card component and we can actually close everything else. So now what I want to do is I want to define a constant called progress. And that's going to go over object.values, selected columns, and it's going to filter by Boolean here. Like this, and include.length, like that. And then what we are going to do is we are going to go and find the cancel button. And here we're going to add a new button, which will say continue. but in the parentheses, we're going to calculate, well, not calculate, but we're just going to show progress divided by, but not actually divided, just like, for example, three out of five, something like that. And then inside of here, we're going to say required options.length like this. There we go. So you can see that now it says four of three. Okay, so maybe not the best logic to handle this. So what we can do for now to simplify it is go inside of the table head select component and just remove the notes for now. Right. So make sure you only have these three, which are exactly the same as these three in the required options. So that will simplify things for us. And then we can also go ahead and give this button a disabled property. if progress is less than required options.length like this. And let's go ahead and give it an on click like that. Or we already have a, we have on submit, but okay, we're going to handle that later. So let's just see if this is working. So if I import a new CSV, there we go. So continue zero out of three. But if I add a date, that's one out of three. If I add payee, that's two out of three. And finally, I add an amount and that's three out of three. If I reset something to skip, I get back to two out of three. Perfect. That is exactly what we wanted to do. Let's go ahead and let's give this a size of small while we are here. Great. Now, I just want to slightly modify how this looks on mobile, right? This cancel and continue button. So I think that we can do this. We can do flex call, and then here we can do LG flex row, like that. And then we can do gap Y2 here. We can give this button a class name of... Let go ahead and just collapse all these items here So I just wanted to look a bit nicer and more consistent on mobile So full width on mobile but on LG on large is going to be auto like this And then I want to copy this and paste it for the button below. There we go. So now when I expand, perfect. Now this looks a bit more consistent. Not perfect, you know, but consistent. And I want to do the same thing here, right? It looks a bit weird. So let's just fix that as well. So let's go inside of page.tsx right here. So inside of here, we do the same thing. Let me just collapse these two. Let's give this a class name of pull width, but on LG auto. And then let's give this a flex call but on LG flex row. And let's give it a gap Y of two. There we go. So now this two is also consistent. And when I import, this is also consistent. Perfect. So what we have to do now is we have to convert or format these values from, well, this matrix of strings into something that we can send to our backend. And we know what our backend expects. So if we go into transactions API route, and if we find the bulk create right here, it accepts an array of these objects right here. So that's what we're gonna have to do here. So let's go inside of our import card here. And now we're going to go ahead and develop this on click method right here. So that's going to be our handle continue method. So we're going to do that just below the progress. So const handle continue here is going to be an empty arrow function, which will, first of all, create a method, get column index, which we're going to reuse here. So we're going to accept column string. And we're going to return column dot split underscore and the first one. Basically, you know how we name our column and then we do column index. So now we're going to have to keep doing this. So I don't want this to write that many times. So let's just create a little method to help us with that here. And now, well, here comes the bit of a confusing part. I mean, everything was confusing, but this is perhaps the most confusing part so far. So basically, we now have to turn this into from this weird array, right, into our very simple array of objects, right? And we also have to create each of these objects inside because currently we don't know what's the date, what's the category, what's, you know, the amount, none of those things. So first, let's create a more structured mapped data here. So it's going to be an object which will have headers. This headers will go headers.map. And we are going to do underscore header because we don't need it. We just want the index. And inside of here, we're going to get our column index for this specific header. So what the user selected in the select field will be get column index from our method above. And we're going to pass in the column underscore index like this. And then we're going to return selected columns. We're going to find column underscore column index here or null. So that's the headers. And now for the body, we're going to do a similar thing. So body.map, we get the row in here. And first we have to transform the row. So const transformed row is going to be row.map, get the cell, get the index here. Let's do the same thing. So let's get the column index. and let's return selected columns, backdix, column, underscore, column, index, like this. And then if we have that, we're going to return cell. Otherwise, we're going to return null. So we have to return the value of what's in the cell, right? But only if we have that selected and matching under this column index. from that header, right? So it's confusing. You have to keep thinking of that matrix, right? And then we're just going to do a quick little check here to return transform the row dot every and we're just going to ensure that item is not empty. So if item is null, return an empty array like this, otherwise transform the row because I found that there is a possibility of a bug here. And then we'll just add a final filter here to only show full length rows like that. So very, very confusing, I know. But let's make it easier for us and let's at least console log this so we know what just happened, right? And now let's use this handle continue and let's add it here like this. so I'm going to expand this and let's see if this will so make sure you have selected the date the payee and the amount let's click continue and now we have mapped data here so there we go you can see now I have headers so I skipped the first one I skipped the second one and only on the third one I selected the date so now I transferred my data here to only pick those exact things from my CSV file, right? Do you kind of understand what we did here? Obviously, it's still very confusing, right? Because you keep half the thinking about how that looks in a matrix, right? So we only populate and transform the body fields which have the matching headers selected, right? I know it's complicated, but again, I don't know if there's an easier way to do this, right? It's simply a complicated thing to do. But we are still not done. So now that we have the mapped data, we have to transform it into something our backend can accept. So let's do const array of data here to be mapped data dot body dot map row, return row dot reduce, get the accumulator, which can be any, then get the cell and then get the index. And inside of here, we're going to get the header. So header is going to be mapped data dot headers, and it will match the same index because we just took a look at the console log. So we know that the indexes match. Now, in case header is not null, we are going to add to the accumulator that header and that cell. So we are going to create a proper object like date equals and then a date string, right? Otherwise, we're just going to return accumulator. And let's start with an empty object in the beginning of the reduce method like this. And let's console log this now. So array of data. So this will now be familiar to something that we can accept in our backend. So if I click continue again, there we go. This is now something that we can send into our backend. Much, much better. Great. But we're not done yet because we have to format this amounts into milli units. So let's go ahead and do that. So the last formatting method here is going to be our const formatted data. Array of data dot map. Get the item here. And we're going to spread everything inside. And then we're going to get the amount to be convert amount to million units parse float item dot amount. So make sure you've imported convert amount to million units. Let me just add this here or here. It doesn't matter really. And we also have to modify the date because it doesn't match exactly what we need. So let's go ahead and just import format and parse from date FNS. So remember, we defined this date format and this output format, right? So your CSV files need to have this date format. Otherwise, it's not going to work. So let's go ahead and do this. Inside of date, we are going to format. but first we have to parse the existing item.date using the date format constant and then new date. My apologies for this popping up. So new date. And then lastly, add the output format like this. There we go. and let's console log our, my apologies, formatted data finally. My VS code started getting slow over how many code we have here. So let's finally try this out. Continue. And this is the formatted data. So the exact dates that we need. And let's look at the amount. It's converted to million units. So this is something that we can now finally send to the backend. The only thing that's missing is which account do we make this for? So that's what we are going to do next. But this is ready. We can now do on submit formatted data. So if your formatted data looks like this, you are ready to send this further. Great. So what we have to do now is we have to create a dialogue which will confirm this formatted data by selecting an account. But I want to do that in the next chapter because we are already almost an hour in. So I'll give you some time to take a look at what we just wrote because we wrote a ton of things. Great, great job. So we left off at completing the import functionality and giving our table the ability to select a date a payee and an amount and to click continue So now we have to go further and we have to go back inside of our dashboard transactions specifically page So inside of here, we have this on submit method, which instead of import card is used right here. So we have handle continue method where we worked last time. We created this mapped data, then we created the array of data, and then we created the formatted data. And last thing we did was we passed it into the onSubmit formatted data. But our onSubmit here is empty. So let's change that and write onSubmit import. And let's go ahead and write this method right here. So I'm going to do that right here at the bottom. OnSubmit import is going to be an asynchronous method, which will accept the values. and the values are going to be a type of transactions dot, let's import transactions first. Let's see, transactions, do I have, oh, so I'm using this as transactions. All right, so this is what we are going to do then. Let's go inside of here and let's import from add slash database schema. Let's import the transactions as transactions schema. So we have to add an alias here. Or is that not working still? Let's see. Oh, yeah. So I have to open this method here. Basically, the problem is we cannot have a transactions import because we already have a constant here named transactions. So that's why I'm importing transactions as transactions schema right here. So let's go ahead and make this a type of transactions schema dot infer insert, but make it an array of items like that. So those are the values. and then inside of the method here, what we would do is we would submit this to bulk create mutation, right? But we can't do that yet. The first thing we need to do is we need to open a model and confirm the account where this is supposed to go. So for that, what I want to do is I want to create a hook similar to my use confirm hook. So let's go ahead inside of hooks, use confirm and let's copy and paste this and let's call this use select account. But what I'm going to do is I'm going to move it out of hooks here. So I'm going to cut it and I'm going to put it inside of accounts hooks because it just makes more sense to be in here for this entity. So not here. Let's go inside of the use select account and let's go ahead and modify it. So first of all, this use confirm will be called use select account. and we are not going to have any props in here, right? But our return method will stay the same. So JSX element and promise unknown right here. And now let's go ahead and let's just add some slight changes here. So let's add const account query here to be use get accounts. Let's go ahead and add const account mutation to be use create account. and let's define const on create account to accept a name, which is a string and passing the account mutation.mutate and passing the name. So that's going to be our method here. And const account options here are going to be a check of account query.data or an empty array.map, get the account, and return an immediate object with label account.name and value account.id. So we are preparing for our select component here. Now let me go ahead and just use these two imports and move them above all components because they are features, and I'm going to change the imports to stay consistent. There we go. So use get accounts and use create account. You don't have to, of course, change that. If you want to use dot, dot, feel free to use dot, dot. Great. So now I'm going to go ahead and do the following. So just below the const promise, I'm going to add a control for our value, but I'm going to use ref for that. And I'm going to explain why. So let's add select value to be use ref from React with the default value of string. Well, with an accepting value of string, right? And make sure you've added use ref from React here. So that's going to be our select value here. And now let's go ahead and let's go inside of handle confirm. And instead of sending true, we're going to send select value dot current because we're using the ref, right? So let's go ahead and also modify this. So our promise inside of we have this type here. So let's just go ahead and slightly modify the value here. So it's not going to be a Boolean. It's going to be either a string or undefined, right? Like this. So now you should not be getting an error here. And now let's go ahead inside of here and let's send undefined if we cancel. Great. And instead of having a dynamic title here, we're just going to render select account. and instead of having dynamic description here, we're just going to have please select an account to continue like this. And this can stay the same, but we are going to modify, well, one thing. We're going to leave the footer, but just in between the header and the footer, we're going to add a select component from components select. So not from components UI select, from components select. This is that component which we created, which can accept the onCreate method so that we can search and create new ones. So that's the one we need. So make sure you've added an import for that component select. Let me move it here. All right. And now let's go ahead and pass in everything we need. So placeholder will be select an account. And then options will be account options. and onCreate will be onCreateAccount, and onChange is going to be a method which takes in the value and is going to use the ref, selectValue.current, to be the value, and disabled is going to be if accountQuery is loading or if accountMutation is pending. Not is paused, it's pending. There we go. and I think everything else can stay the same. So the reason we are using a ref here and not a value, not a use state is because changing the state causes this hook to re-render which will cause the flashing effect over this dialog. So every keystroke would cause a flashing effect. So that's why we are using this in a ref. Great, so we have the use select account And now let's go back inside of our transactions page here. And let's go ahead and let's use this use select account. So I'm going to go ahead and find my hooks here. And I'm going to go ahead and use use select account from my features account hooks. So let me go ahead and add this here. So I only have these features. So there we go. I'm going to separate the features for accounts here because all of these are transactions. so we now have use select account and from here we are destructuring the account dialog and the confirm method so now we have to render this account dialog and we can render it here in the variance import because it's only going to be used here either way so account dialog added inside of this fragment here with the import card and now inside of the on submit import what we are able to do is you're able to request the user for the account by doing const account ID await confirm like this. And then if we don't have an account ID, we're going to return. So we break the method and we are going to return a toast from Sonor with an error. Please select an account to continue. Make sure you've added an import for toast. So I'm going to add that to the top here. Great. And otherwise, let's go ahead and let's get the data to be values.map. We get the individual value. We spread the value and we attach the account ID as string to all of our data. And now we are ready to create a bulk mutation. so let's go ahead here below use new transaction and we're going to add bulk create mutation so that's going to be use bulk create transactions here make sure you've added an import for use bulk create transactions right here all right so again this is some type script weird bug when I refresh, there we go, no errors here, or there are errors. All right, so let's see what's going on here. Use bulk create transaction. Features API, use bulk create transactions, and this is bulk delete transactions. Inside of here, it looks like we are having some circular references here. So let's see why that is happening. use bulk create transactions. Okay, let's go ahead and see if I made any mistakes here. It looks like my use bulk create transactions is incorrect. And all I did was restart my VS code and the TypeScript errors went away. So it looks like it was the TypeScript server. Okay, so I didn't change absolutely anything. Let's go back inside of use bulk create transactions here. And now we will be able to do that. So we have bulk create mutation, or we can call it create transactions. So it matches the delete transactions here, right? So what we're going to do here is going to do create transactions that mutate. We're going to pass in the data and on success, what we're going to do is we are going to call on cancel import.\nso we close this view. So I want to test this in the following way, and I recommend you do the same thing. So don't rely on the transactions table, because remember, we only loaded the last 30 days. So go ahead and run a BUN database studio here. So this way you will be able to track all the data. So I'm going to go ahead and open my Drizzle Studio, and I'm going to go ahead and remove all of my transactions, So it's easier to look at. So now my transactions are completely empty. So I'm going to import a statement. I'm going to select this for a date, this for payee, and this for amount. And if you want to, you can also open the network tab so that you can keep track of what's going on. So let's go ahead and let's click continue. And there we go. We have an account. Let's select this into a new account. Let's click confirm. And there we go. So it works flawlessly. bulk create, sent the data with an ID of all of our items, and we got back a response of those created items. So as you can see, I only have three of those here. You might not have a single one because you can see these are in April. So a lot of those from my import are not here because I only load the last 30 days. So I'm going to go inside of my Drizzle Studio instead. And there we go. So in here, I can see all of those elements. I think it was 23 of the 21 of them. Great. So we just wrapped up the import. Great, great job. And now you can go ahead and categorize these if you want to. There we go. So now they work much, much better. Perfect. So what we're going to do next is we are going to go ahead and finally work on the overview page because we now have transactions with categories so that we can create some statistics. And then we are going to add the filters, which will then be used for this page as well so that we can change the date if we want to increase from just 30 days and also load only a specific account, for example. Great, great job. So now that we've wrapped up the transactions page, let's go ahead and do the following. Let's create an API endpoint to create a summary of our transactions and categories. So here's what I want you to do first. I want you to have at least a couple of transactions in your recent dates, right? So make sure it's in the last 30 days. So for me, it's 10th of May, which means everything in the past 30 days, I can select here. So I'm just randomly gonna add some things with some incomes and some expenses here. So I basically want you to have some items here. Because remember, only the last 30 days will be calculated and shown here. And now switch to overview. And let's go ahead and let's develop. So you can shut down the Drizzle Studio. No need for that now. Just keep your app running right here. Let's go ahead inside of our app folder API route. And let's go ahead and let's create summary.ts. And inside of this summary, let's go ahead and let's import Ono. from Hono, and let's write const app, new Hono, and let's write .get, slash, and for now, we can just add a very simple controller, summary, true, like this, and let's export default app. Now let's go inside of a route.ps here and let's chain our summary. So we are going to have to import summary, ron.slash summary like this. So I'm going to make this first so they'll have the same increasing length. There we go. It doesn't matter, right? I mean, the order of your chaining matters if you have same named stuff, but all of our are named differently, so it does not matter. So now you should be able to go to localhost 3000 slash API slash summary, and you should just get summary true inside. That's it. And this is going to be our only endpoint in the summary routes. So go inside of summary and let's go ahead and let's develop this. First of all, we're going to add the clerk middleware here from HonoClerkOut right here. Then we're going to add a ZValidator from HonoZodValidator. We're going to validate query field and we're going to use Zod. So import Z from Zod, object from Z.string.optional. And then we're going to have two Z.string.optional. and we're going to have account ID z.string optional as well. There we go. And now inside of this asynchronous method, let's go ahead and let's get the out using get out C and we need to import get out from Hono Clark out. Let me just change these imports right here. There we go. So we can also immediately destructure everything we need from our C request valid query. So that's going to be prompt to and account ID. Now let's check if we don't have out question mark user ID. In that case, we can return a C dot JSON with an error inside saying unauthorized. And let's mark this as 401. one. That's very important to mark this as an error. Great. And now inside of here, let's go ahead and let's write const default to to be new date, then const default from to be sub days from date FNS. So make sure you've imported sub days from date FNS. And let's also add parse and difference in days from date FNS. So sub days, parse, and difference in days. So inside of here, we're going to sub 30 days from the default to. And then in here, we can go ahead and we can create our start date. So if we have from, in that case, we're going to parse from using our format. So year, month, day, new, date as the third argument. Or otherwise use the default from. And then inside of here, let's define our end date to check if we have two. If we have two, we're going to do the exact same thing here, but we're going to pass two. Like that. Otherwise, default two. There we go. Now let's define period length to be difference in days. Because remember, it doesn't have to be 30 days, right? If user selects their own period, it can be 40 days or 15 days. So end the date, start date, and plus one for the offset. Then let's do last period start. So every time we calculate the summary, we're also going to calculate the period 30 days, well, not 30 days, but the equivalent amount, but from before that, from the period before that, so that we can show the user, all right, if you selected last 15 days, you made this much money, but that is 5% less than the previous period of that same length, right? So that's why we want to calculate the previous period as well. So we're going to sub days inside of here, start date and period length. And then let's define last period end to be the end date and sub days as well. There we go. Great. So now I want to do, I want to create two things. So I want to get the current period from something, right? and I want to get the previous period or last period from something as well. And this something will be our await fetch financial data method, which we don't have yet. And this fetch financial data will take three arguments. The first one will be out.userID. The second one will be start date. And the last one will be end date. So these ones right here. So I'm passing in alph.userid. You don't have to, obviously. But if you want to extract this fetch financial data in another folder or file, then you're going to have to pass alph.userid. But for simplicity, I'm going to write it inside of here. So obviously, if you write it here, you don't need to pass it as an argument. But just in case someone wants to copy it somewhere else, I'm going to leave it like this. Now let's create this asynchronous function, fetch financial data. So the first one is going to be user ID, which is a string like this. The next one is start date, which is a date and end date, which is a date as well. Like this, let's open this method here. And inside of here, we're going to return await database from database drizzle. So just make sure you've added this. And we're going to select the following, we're going to select income. We're going to select expenses and we're going to select remaining. So I want to know how much money we've made in this period. So for that, I'm going to use SQL from Drizzle ORM. So make sure you've added the import. I'm just going to move it here. I'm going to open back this and I'm going to sum case when transactions. So we have to import transactions from database schema. Make sure you add this. So transactions.amount like this is higher or equal than zero. So that's my income, right? So then we're going to continue writing. We're just going to zoom out so we can see how this looks like in one line. I'm going to zoom back in later. So if it's higher than zero, then this is my income. So then I going to do the transactions like that Else I going to write zero and end And then I going to add dot map width and pass in the number like this So let me go ahead and try and zoom out one more time. Hopefully, so you can see it in one line. All right, I'm not going to zoom in any further. So let me show you how this looks like in one line when I have to scroll like this. Maybe this is easier for you. All right, I'm going to turn WordWrap back on so it collapses now. Great. So we have the income. And now we're going to go ahead and copy and paste for expenses a similar thing. So this is going to be the opposite. So it's going to be some case when transactions.amount is this time less than zero, right? So very similar, but a different clause here. and else zero can be stay and it stays the same and map width is still the number. And then we're gonna have a last one, which is gonna use the native, well, not native, but the drizzleRM sum. So let me just show you, we have SQL and sum imported here from drizzleRM. So in here, we can just use this sum because it's a simpler method, transactions.amount. That's it, map width, number. No high logic here, right? We just have to count everything. And now we have to specify from where. So from transactions, we already added that import. But remember, transactions don't belong to users. They belong to accounts. So we have to create an inner join of accounts. So we're going to write accounts from database schema. So make sure you add accounts here. And then we're going to write equals. So import equals from drizzle.oram. And inside of equals for the first argument, And I'm going to pass the transactions.accountId. And then for the second one, it's going to be accounts.id. Like that. So that's going to be the inner join. And then we can chain where here. And in the where, I can add and from drizzle ORM. So make sure you've added and equals, equal, and sum. And in this and, we are going to add for the first argument, a check if we have an account ID. If we do, we're going to add equals transactions.accountID with accountID, otherwise undefined like this. Let's go ahead. Did I add and, whoops, how did this happen? I don't know. So and that's what I need. And equals SQL and sum. There we go. And this is accountID with a single C. so that's the first argument then we're going to have equals accounts dot user id to be out user id actually not out user id but instead it's going to be this user id here from the function in case you want to extract that right so it's easier great so we have that and then we're going to add greater than or equal from drizzle ORM for transactions.date and start date. And we're going to copy and paste it and make this less than or equal from drizzle ORM for end date. So just ensure that you have end equals greater than less than equal SQL and sum from drizzle ORM. There we go. So that is our query here for the periods. So if you want to, you can already try and return this. So return c.json. Let's return the current period and the last period. So let's see if I refresh this. We should now see some changes here. There we go. So you can see my total income, my expenses, and how much I have remaining, right? Obviously, these are milli units, So they have to be divided by a thousand to be presentable on the front end. But we can already calculate something in this period. That's why I told you to have some transactions in the period, right? Otherwise, it's not going to work. There we go. Great. So now I want to create a method that will help me calculate the difference from the current period and the last period in a percentage. So let's go inside of the utils folder and let's export function calculate percentage change. So that's going to accept current as a number and previous as a number as well. And inside of here, what we're going to do is we're going to check if previous is equal to zero. In that case, let's check if previous is equal to current. In that case, the result is zero. Otherwise, it's 100% increase. Otherwise, let's return open parenthesis, open double parenthesis inside current minus previous like this divided by previous. And we are going to multiply it by 100. There we go. Perfect. So now we have that change. And now inside of here, we can define the following. We can write const income change and we can pass in the calculate percentage change from our lib utils. So just make sure you've added the calculate percentage change here. So we're going to open this and we're going to pass in the current period income. And last period income. You could, of course, you know, try if you know SQL better than me. Feel free. I don't know SQL that well. So if you know a better way to calculate this inside of SQL, go ahead and do that. It's probably much faster, much more optimized. But I'm satisfied with at least not using Prisma-like queries. So this is a big step for me. But if you know SQL, better feel free to try and do that here, what I'm doing here with this percentage changes. So the next one is going to be expenses change. so I'm going to be looking at the expenses here like this and the last one is going to be the remaining change so remaining there we go so if you want to you can now add in here the income change the expenses change and the remaining change to see exactly how much in percentage this has changed over the last period. Perhaps your, for example, mine are zero here because I just don't have that many transactions, right? I have a very small amount of transactions. So there's nothing to compare with the last month. But I think that if I go into my localhost 3000, if I go into transactions, for example, and if I add one from previous month, from maybe March, for example, if I tried that, and if I for example make an expense of $500 it's not going to be visible here but I think that maybe if I did this correct maybe it's going to be visible here it's not okay we're going to see that later when we create a seed script which is going to be able to fill from months before and up till now so we're going to see this change working okay let me just confirm that we didn't have a bug here. All right, I think there's no bug. We just don't have enough data. Great. And now what I want to do is I want to create queries to show the user how much they spend by category. So we're going to be using the group by element. So let's go ahead and write const category to be await database dot select. We are going to select the name, which will be categories.name and we have to import the categories from database schema so just make sure you've added this besides the name we are going to add the value which will be a SQL operator sum absolute and then we're going to add the transactions.amount.mapwithnumber and then we're going to write from transactions inner join so we want to we're going to create two inner joins now right we're going to create an inner joy for accounts with an equals here of transactions.accountid and accounts.id but we also want an inner join for categories usually we use left join for categories, but in this case, the query won't make sense if we load something that doesn't have a category, right? So let's go ahead and join the categories using transactions.categoryID with categories.id. Let me just see. Oh, I'm missing the categories in the inner join. There we go. So this is accounts, account ID, accounts.id, categories, category ID, category.id. There we go. And then let's do a where here. And in the where, we can actually copy it from the one above. So we save some time. This where. So let's change that. So the user ID method here doesn't exists. So it's going to be alf.userID like this. The account ID logic stays the same. The equals stays the same, but we are going to add one more thing, which is less than transactions.amount0. So we only want to count the positive category spend. So we have to import larger than in drizzle orm larger than right here there we go all right we have larger than here so we are only loading those categories right and now we have to finish the query by grouping by category name right so i only wanted to display you know travel and then the amount i don't want an array of items where travel repeats multiple times i'm going to group it by name so it aggregates by only a single name. So it's only going to repeat once, right? And then let's order by and let's go ahead and write descending from drizzle or rem. So make sure you've added this import and we're going to write a SQL here. Some absolute transactions dot amount. is this good enough let me just see cannot find a sequel oh again something happened My apologies I keep somehow doing this There we go. So now I think this should be just fine. So let's go ahead and do this. So I'm never going to send back all the categories because if there's a ton of them, right, I don't need them. So I'm going to write const top categories to be category.slice0 and 3. and const other categories are gonna be category category dot slice three, right? So I am doing the constant category here. So make sure you don't accidentally do categories because that is our schema, right? So make sure you are doing this on this constant where we did the query for categories, great. So now I want to keep everything else as well, but I'm going to keep everything else in the other sum. So that's going to be under other. I only want to display the top three most spent categories and everything else will be under other. So I'm going to write other categories, which we just have here, dot reduce. I will get sum and current. And I'm just going to reduce sum plus current dot value, beginning from zero. there we go perfect and now let's go ahead and let's finalize this by writing const final categories so that's going to be top categories like this and then let's do if other categories dot length is less than is more than zero final categories that push name is going to be other and value will be the other sum. So no point in showing the other categories if there are only three categories, right? So we do this little trick here by assigning this as an array and then we add an additional one if other categories actually exist. So we can now add the final categories here to the change as well. So let's refresh this to see what we spend our money on. There we go. So this is the most spent category that I have chosen, right? So I only chose this one. So I think that now if I try, and for example go inside of transactions and if I go inside of add new if I pick a date for example a few days ago let me go ahead and create food here test and I'm going to spend a thousand dollars on food and I think that now this might be visible in the summary let's see all right it still doesn't seem to be visible I'm not exactly sure why oh i realized why i also think i found a bug here so i just modified this food category to be in a half a million dollars and also we have a bug here we forgot to change our edit transaction so it has this we have to convert from million units somewhere but let's do this uh let's go ahead and create for example five thousand dollars but make sure it's an expense right that's the important part. So now if I go to slash API summary, it should be here. There we go. So we spent a lot of money on food. So it's working. Great. Okay. Fine. And now the last thing that we have to do is we have to create data for our graph to show how much we spent by day in this period that the user selected. So const active days is going to be await database dot select. Date will be transactions dot date because we are going to group by date later. And then income is going to be SQL some case when transactions dot amount is higher than greater or equal than zero in that case we're going to sum transactions.amount else whoops else zero and map with number so let me zoom out so perhaps whoops where was I? my apologies so it's a very similar if not exactly the same query as the income we had in the beginning when we were calculating the income the expenses and remaining right so some case when transactions amount is greater or equal than zero then we use transactions.amount else zero and map with number and then you can go ahead and copy and paste this one for oops for expenses so for each day we're going to calculate the income and the expenses so this one will only calculate if the income is if the amount of that transaction in those days was less then zero. Everything else can stay the same. There we go. So let's now go ahead and do, well, the same thing we already did a couple of times. So from transactions, and then let's inner join the accounts. So let's copy and paste that. So we save some time. From transactions, inner join with transaction account. There we go. We can also copy the where here. So we are checking by account ID here. So let's just confirm that. We have account ID. If we do, we do the equals. Otherwise, we do undefined like that. And we confirm that these are only the user's transactions. So this is kind of the most important equals sign here. We don't need this less than that to be removed, but we do need greater than or equal and less than or equal. And finally, let's do group by. So we aggregate by transactions.date. So we don't have repeated dates. And the last one is to order this by increasing dates. So transactions.date like this. And I somehow did this again. Okay, there we go. The problem now is that this will only generate data for the days that we selected, but only those that actually have some transactions. So I'm going to add active days here. So when I refresh inside of this API summary, there we go. So we have these dates, but that's not enough for us to fill a useful graph. I also want to fill the dates where we have nothing, right? So we're going to create a method for that called fill missing days. So let's go inside of our utils and let's write export function fill missing days. So the first argument will be active days, which is going to be an object and an array of those objects. Inside of the object, we accept the date, income, which is number, and expenses, which is a number as well. Then we have a start date, which is a date, and an end date, which is a date as well. Now let's go ahead and let's import each day of interval from date FNS. So using that, we'll be able to generate the rest of the days here. So first of all, if active days.length is equal to zero, we just return back an empty array, right? Then let's write const all days. to be each day of interval. And let's pass in start to be our start date and end to be our end date. And then we can say const transactions by day to be all days.met. Get the individual day. Open a function. Let's attempt to find this in our existing active days, which we passed here. So active days dot find D short for day is same day using date FNS. So we don't add duplicates here. So is same day D dot date and day itself like this. And now we're going to write if found return found. Else we are going to return an empty day. So date will be this date, income will be zero, and expenses will be zero. So you're going to see why we are doing this in a second. Let's return our transactions by day. So remember, this is how it looks right now. So we only returned three days, even though we selected 30 days, right? So our graph needs to show 30 days, right? So let's go inside of our summary method here and let's go ahead and let's finally write const days to be fill missing days and pass in active days start date and end date like this and make sure you've imported the fill missing days from lib utils. and then inside of here we can go ahead and we can send days instead of active days here. Let's go ahead and refresh this and let's see this now and there we go. You can see how now we have everything we need for our graph and only some days have values but most of these well don't have anything. Great so this is what we wanted and that's it for the summary endpoint here. So let's just go ahead and style this better. So we're going to have data first. It's going to start from data so our API structure stays consistent. Then we're going to have remaining amount. So that's going to be current period dot remaining. And then we're going to have the remaining change below that. Then we're going to have an income amount. That's going to be current period income. Then we're going to have income change. Then we're going to have expenses amount. That's going to be current period dot expenses. And then we're going to have expenses change. So I'm just bringing things back that we are going to need for the dashboard. And then I will need the categories, which will be my final categories. And I will need my days, which will be my days. They are the same named constant. There we go. So we've wrapped up our summary API endpoint. So if I refresh, there we go. This is enough for us now to display on the dashboard. Before we wrap this up I just want to go ahead and fix this million units issue that we have So when I select something that has an amount of and I click edit in here it appears as half a million dollars So I think I know exactly where the culprit is Let's go inside of our features, transactions, API, use get individual transaction. And instead of returning data here, we're going to return an object where we spread the data and get the amount and write convert amount from millianits, data.amount. And make sure you've added an import to convert amount from millianits. Let's try this again. I'm going to close the drawer and refresh. There we go. My input, my transaction for $500. When I click edit, it has $500 here. Perfect. Amazing, amazing job. What we're going to do next is we're going to create the, well, all the elements needed for the dashboard, which will work with the summary API endpoint. So I was experimenting with our summary endpoint and I noticed we do have a bug when it comes to calculating the percentage change of the last period. As you can see, I have added a bunch of transactions and I have a bunch of transactions in the last period, but still all of my expenses change percentage is zero. So I've noticed a bug inside of our summary endpoint. we don't use the last period start and last period end. So go ahead and find last period, fetch financial data. And instead of start date, we have to use the last period start and last period end like this. So now if you have enough transactions, there is still a chance that you still get zero and it's correct, right? Don't worry. But I have added a bunch of transactions and there we go. Now you can see exactly how much it changes in percentages here. Great. So if you're wondering how I filled with this much information, it's thanks to a seed script. So we're going to go ahead and create a seed script now. So you can copy this from my source code. Go ahead inside of your scripts and create seed.ts right here and copy and paste this from the source code. you can also pause the screen. I'm going to go slowly here if you want to take a look at what's going on, but there is a lot of change going on here, right? You don't need the seed script at all, but if you just want to get a bunch of data, you can use this seed script. Only thing you have to change is the seed user ID. So let's go ahead and obtain that. So you're going to go inside of your clerk dashboard, go inside of users, find your user, the one you want, and copy that user. And then go ahead inside of here and replace this seed user ID with that ID. And then let's go inside of our package.json and let's go ahead and copy and paste this. So you didn't have to do this, right? You can continue the tutorial without it. It doesn't matter. But if you want to have a bunch of financial data, you can use the seed script. and now let's go inside of the terminal here. Let's shut down the app and let's run run database seed like this. And this will just, if there's no error, that means it successfully created a bunch of data. So if I refresh here, I should have some different numbers here because it reset everything. There we go. So now I have different numbers and I have all these categories. Great. So I think I've noticed one more bug here inside of my summary. And that is this, calculate percentage change. So I have a typo, calculate. This is how it should be. So I'm just gonna find all instances and replace that typo. And let me reload my window to fix the type script error. There we go. So some slight improvements on our summary API endpoint. And now let's go inside of our app folder, dashboard page.dsx. But actually before we can do anything here, let's go ahead and let's create our features. So I'm going to go ahead and create the summary feature here, API, and I'm going to create use get summary.ds here. And we can copy the transactions. So API use get transactions. We can copy this because we're going to need this params. So paste that inside of here. Let's rename this to use get summary like this. We can remove this to the key. Well, we can leave it actually, yeah, because we're also going to use those here. So we needed to use search params. We need the from to and account ID, but our query key here is summary. And we are calling the summary here. And the query should be fine because our summary endpoint accepts the same things, from, to, and account ID. And this will be fail to fetch summary right here. And now we have to modify the data a little bit. So the data is an object. So we're going to go ahead and spread the data here. And then we're going to add income amount to be convert amount from million units, data income amount. then we're going to do the same thing for expenses amount, and then we're going to do the same thing for remaining amount. And let's see what else we have to do. So we have to do the same thing in the categories. So let's go over categories and write data.categories.map. We get the individual category, and we return an immediate object where we spread the existing categories, categories but change the value to convert amount from milli units into category dot value like that and we have to do the same thing for the days so data.days.map we get the individual day we open and return an immediate object we spread what exists in the day and modify the income to convert amount from milli units. So day.income. And we do the same thing for the expenses for that day. That is it. Make sure you've added convert amount from milli units here. Great. So we have our use get summary here. And now we can go ahead inside of the app folder, dashboard page.tsx, finally. So let's remove use client, let's remove the button, let's remove everything here. We're not going to need pretty much anything for now. We can change this to be our dashboard page. And let's give this div a class name of max width screen to excel mx auto pull width padding bottom of 10 and minus margin top of 24. And then inside of here, we're going to use the data grid component. So let's go ahead inside. Well, we can go ahead and create that in the components, for example. So let's go ahead and create data-grid.psx like this. Let's mark this as use client because the parent is a server component, right? It doesn't exist. So let's do export const data grid here. Let's return a div data grid. Let's go ahead and let's import data grid from components data grid like this. So now on my localhost 3000, let me just refresh. There we go. I should have data grid written right here. So what we are going to do is we are going to go ahead and we're going to create a component called data card. But just before we do that, let's go ahead and give this class name a grid. Grid calls one. LG grid calls three. Gap of eight. Adding bottom of two. And margin bottom of eight. Like that. And now let's go ahead and let's add the following hooks. So let's add use search params. usage params from next navigation. This is the one. And let's get the params. Use search params. Let's get to. And let's make it params.getTo or undefined. And we're going to do the same thing for from. Like this. There we go. And now let's go back inside of the data grid component. Oh, we already are here. So let's go ahead and do the following. We're going to create a little util in our libs, in our utils. My apologies. And the util will be called format date range. So it's going to accept a period, which will be an optional period. And we're going to define type period here. so that's going to have a from which can be either a string or date or undefined and the same thing will be two so that's the type period and now let's go ahead and open this method and inside of here let's do const default to to be new date const default from to be sub days from date FNS, default to 30. So the same thing we keep doing on the back end, right? We are creating a default range. So make sure you've imported subdays because we just added it here. Then we're going to check. If we don't have a period from, the format date range is going to return a label, which will simply say format, again, from date FNS. Make sure you've added this. okay, right here. So it's going to say format default from in a format of LLLDD. And then we're going to add to. So this will be like a to, right? From to format default to in the same LLLDD, comma, Y. So we also want to add the year at the end. No point in showing the year here. so we can save some space. We can only show it in the last one. Like this. There we go. So I expand it so you can see it in one line. Like this. There we go. And now we can copy and paste this. And this if clause will say, if we have period.2 right here.\nThis will then do period.from and period.to. Again, this is how it looks like in one line. And the last one will be the simplest one, return format period from and llbd, y. Like this. So we are creating a readable range so the user knows which dates are selected. So const date range label will now use format date range from lib utils. And we're going to pass in our period, which will be to and from. Like this. Just make sure you've added this. And I think you can already try this out. So if I render this here, there we go. So the last 30 days from April 10th to May 10th. And today is May 10th or 10th of May. Right. There we go. So now we have to create a component called data card. It's going to be a self-closing tag and it will accept the following props. The title, which will be remaining. Let's see, it looks remaining. Then it's going to have value, which will be data, question mark, remaining amount. Oh, I forgot to add any data here. So we are going to have, of course, use get summary. from features API use get summary. And in here we can get our data. There we go. So now data should have the remaining amount. There we go. Perfect. And then we're going to have the percentage change, which will be data. And in this case, it's just gonna be the remaining change. Icon will be FAPigibank. so let's go ahead and add this clear bun add react slash icons so we need that alongside our lucid icons because this ones have a specific look so let's do bun run dev and then we can import fa piggy bank from react icons fa like this there we go and variant will be default and date range will be our date range label there we go so now we have to create this data card component so let's go ahead inside of components and let's create data card dot dsx let's go ahead and let's create the export const data card here. And let's return a data card. Let's go back inside of the data grid and we can now import data card from dot slash data card, but I'm going to change it to use components data card. There we go. Now we have to create the props for the data card. So for that, we are also going to have to create the variance. So I'm going to be using the inspiration from ShatCNUI. You can see how inside of components UI button, this is how they create the variants, right? Using this default destructive outline. So I want the same thing. So let's go ahead and let's import what we need. So we are going to need the CN library from lib utils. And we are also going to need the variant props from class variants authority and CVA. We're also going to need icon type from React icons, but no need to go to slash lib. You can just import it directly like this. All right, I think that should be okay for now. Now let's create a box variant, which will be CVA. Let's give it some default props, rounded MD and padding of three. open an object and write variants. And then inside of here, we are going to have the default variant. And that's going to be background blue 500 with a 20 opacity. Now the second one will be success, which will be emerald. And the third one will be danger, which will be rose. And the last one will be warning, which will be yellow. So different variants for our card so they can have very different backgrounds. Now let's set the default variants here. Variant, default. So we choose the one from above which we created. Great. Let's copy and paste this now. And we have to do the same thing for the icon variant. So you're going to see in a moment where we're going to use this. So for the icon, the classes are different. This will be size six by default. and inside of here we're just going to be using the fill property and we are not going to need the opacity here like this and the default variant stays the same now let's create a type box variant to be variant props do we have variant props we do have variant props all right so variant props type of box variant, copy and paste this into icon variants. That's going to be a type of icon variant like this. And now finally, we can create an interface, not a type. It needs to be an interface, which will be data card props, extends, box variants, and icon variants. So this way we are going to infer all of these things into TypeScript props, right? So let's go ahead and give it an icon of icon type, a title of string, a value of an optional number, a date range of a string, and a percentage change of an optional number. Let's assign all of those things here. So data card props. And I think we should not be having any errors and we are not perfect. So now we are ready to style this. Let's just extract everything here. So icon will be remapped to icon with capital I so that we can use it as a component like this icon. Right. So that's why we have to rename it here. This is called an alias. And then we're going to add a title and a value which we will default to zero. a variant, date range, and percentage change, which we will also default to zero here. Instead of using a div, we will be using a card from .sgcd or components UI card. Let's go ahead and let's extract everything we need from our card here. So we need card content, card description, card header, and card title. That's everything we need here. So let's go ahead now and let's go inside of here and let's add our card header component. Like this. And let me refresh my localhost so that we can actually see the changes that we are developing. So card header will have a class name of flex, flex row, item center, justify between and gap X4. The card component will have a border none and drop shadow SM. Inside of the card header, we're going to have a card title, which will render our title. And the title itself will have a class name of text to Excel and line clamp one. Let me just refresh this again. There we go. So remaining, I have a typo. Let's go inside of the data grid and fix this. Remaining. There we go. So this will have a title. Then we're going to have a card description here, which will render the date range to tell the user where it's coming from. So this will have a line clamp one like this. There we go. All right. And let's go ahead and do the following. Let's create a div around the title and the description. and let's go ahead and give it a class name of space y2. There we go. So I want them one below another. Perfect. And then what we're going to do outside of this div is another div. We're going to give it a class name, which will be cn. So it's going to be dynamic. We already have cn imported here. By default, it's going to have shrink zero. and then we're going to give it a box variant and passing the variant value inside. And it's also, well, that's it. But inside, we're going to use the icon component. And for it, we're going to give it a class name again of CN and passing the icon variant and a variant prop inside. So basically, we are going to, this is how you use the class variants authority library. So you define your variance inside of here. And then using these props, you then have something called a variant prop. So in here, we don't have it, but you can see how I can extract it here, right? So it doesn't come typed from here. We extend the box variants and icon variants. And we made sure that all of these have the exact same named prop and all the exact same name values. So we can then reuse it in this way. when we pass in the icon variant to the icon component, what's going to change is the fill colors, right? And by default, it's gonna have a size six. So for example, what we could do inside of here is also add shrink zero to the box variant in the default ones like this, shrink zero. And then we would not have this at all, right? It would be even simpler, exactly like the one above. There we go. So you can see how this looks like. Now our piggy bank is blue color. So if I go back to the data grid and change this variant to, for example, danger, now the piggy bank is red and the icon is filled with red color. Or if I go to success, now it is green. Great. So I'm going to go ahead and leave it at default here or you actually don't have to pass it. You can be as explicit or not as you want. Great. Now I want to go ahead and I want to create a count up component here. so let go ahead and do that I gonna go ahead inside of my terminal I gonna do bond add react count up But since this is an older component it doesn exactly work perfectly with Next.js and server components and hydration, but there is a very, very easy fix for that. Go inside of your components and simply create count-up.vsx. Mark it as use client. This is very important. import count up from react count up and export count up as simple as this this will solve any problems with this library now let's go back inside of our data card component and let's go ahead and let's import count up from dot slash count up or components count up like this there we go. So now let's go outside of our card header and let's add our card content here. We already have the card content imported and let's create an h1 element here with a class name font bold text to excel margin bottom of two line clamp of one and break all. Inside of here we're going to pass in the count up value. It's a self-closed component. Let's add preserve value. So on every update, it doesn't start counting from zero, but it just says where it is or it starts counting from the last known value. Otherwise, it will start from zero, but it's going to end from the value that we have for this card. The decimals are going to be two. Decimal places are going to be two. And then we have to pass in the formatting function, which in our case is going to be our format currency method, which we already created in lib utils. So just make sure you import that. So we already use this throughout the project in the transaction, in the columns, right? There we go. So we now have this. Let's just refresh the local host so we see it running. And now outside of the H1 element, we're going to add a paragraph here and we're going to add the following. so we need to create another lib called format percentage so let's go inside of our utils here I don't think we have format percentage we don't so let's go ahead and create that so export function format percentage it will accept a value which is a number and options add prefix which will be an optional boolean and this is a comma here my apologies and by default we're going to give it the value of add prefix to be false. Now inside of here I'm going to write const result and we're going to use a native API. So new intl number format and us open an object style percent dot format value divided by 100. And now that we have the result, if we have options.addPrefix and value is higher than zero, in that case, we're going to show this as plus result. Otherwise, we're just going to return the result because it doesn't matter. The minus will always be shown regardless if we show the prefix or hide it. So now we can go ahead and use the format percentage from here. So just make sure you've imported it from lib utils. And inside of here, we're going to pass in the percentage change from our props here. And we're going to write from last period here like this. So let's refresh this as well. Zero from last period. And there we go. You can see our count up component here working. Let's give this a class name of CN. text muted foreground by default, text small by default, and we're going to have line clamp one, and then we're going to say if percentage change is higher than zero, we're going to mark this as text emerald 500, otherwise if it is lower than zero, we're going to indicate that something is wrong like this there we go perfect and now let's go back inside of our data grid component and we're going to go ahead and copy and paste this two times the second one will be our income so it's going to be income amount and this will be income change and it's going to use an icon fa arrow trend up so let's import this from here actually i think this is imported from another one so import fa arrow trend up from react icons fa6 like this and alongside this we're also going to have fa arrow trend down so those two icons. Okay, FA arrow trend up, that is okay. And this one we say expenses. So expenses amount like this, expenses change, and this will use the FA arrow trend down like this. And there we go. look at our beautiful cards right here. Currently, what looks a bit off is that it stays at zero in the beginning because it's loading. So if you like that, you can leave it like that. But I'm also going to show you how to create a loading component. So go inside of data card. And first of all, add a skeleton from dot slash UI skeleton here. And then go ahead at the bottom and export const data card loading. Go ahead and return a card component. Give it a class name, border none, drop shadow SM, and a height of 192 pixels. Add a card header component. Give it a class name of flex, flex row, item center, justify between, and gap X4. and I'm just going to go ahead and speed things up a bit. So go ahead and copy the, well, I copied it just to speed things up. So create a div with space Y2 and add two skeletons inside with height six and width 24, height four and width 40 here. And then outside of this div, you're going to go ahead and paste and create a skeleton with a size of 12. And finally, outside of the card header, you're going to add the card content with two skeletons, one with shrink 0, height 10 and width of 24 margin modem of 2 one with shrink 0, height 4 and width 40 like this and now you can go back inside of your data grid component and you can destructure the isLoading from here like this and then inside of here you can write if isLoading return and go ahead and copy this div and inside of here you're going to add data card loading from components data card so let me just add these two together like this and let's just align that and go ahead and add three of those there we go let's take a look at this now so you can see our beautiful loading tabs now perfect So what we have to do next is we have to add our data charts. So for days, right? And we have to add one for categories. Great, great job. Now that we have our data cards, it's time to create some charts here. But just before we do that, I want to do a slight modification on this label. Go inside of the data card component, find the format percentage. And remember, we have the options to add the prefix. but I forgot to use that. So I'm going to go ahead, add a comma, add prefix, true. And there we go. Now it will indicate an increase like this. Let's go ahead back inside of our app folder, dashboard, page.dsx, and below that, add a data charts like this. We're going to create a new component called data charts inside of the components folder, data charts.dsx. Let's mark them as use client. And let's go ahead and export const data charts here. Let's go ahead and let's fetch the summary again. So we are going to extract the data and is loading from use get summary. And now let's go ahead and let's prepare if is loading here with a very simple return div loading. We're going to later use skeletons. For now, let's go ahead and let's open up a div. And this div will also be a grid. LG grid calls six and gap of eight. In the first one, we're going to write call span one. On LG, call span three. On Excel, call span four. And inside, we're going to have our chart component with data, which is going to be data?days like this. There we go. Now let's go ahead and set up the component, and let's create our chart.dsx component. So let's go ahead and define the props for this. So it's going to have an optional data, which can have data, a sorry date, which will be a string, income, which will be a number, and expenses, which will be a number. And it's going to be an array of those. Let's export const chart here. And let's destructure the props inside. So we're going to have data, and we're going to default it to an empty array. And let's return a div chart. Let's go back inside of data charts and let's import the chart from dot slash chart or from components chart Let go back inside of app dashboard page and let import data charts from components data charts and you can now safely refresh your local host here and you should have a text here which says chart. Now let's develop the chart component. So we're going to have to use the recharts library. So let's go ahead inside of our terminal and let's do bun add recharts like this. All right, let's just wait a second for this to be added. Let's run our app again like this. And first of all, let's go ahead and let's go inside of the chart component here and let's add everything we need from our components card, right? So card header, card title, and some other stuff here. Great. And now let's go ahead and let's create this. So this is going to have a card with a class name of border none drop shadow MD, sorry, SM. Then we're going to have a card header, which will have a class name of flex space Y2. On large, it's going to have space Y0. On LG, it's going to be flex row. On LG, it's going to be items centered. And justified between will be on both. Inside of here, we're going to have a card title, which will be transactions. like this. So this can stay as it is now. And let's just give this card title a class name of text extra large in line clamp one. Like this. I'm going to add a to-do, add, select. Great. Outside of the card header, we're going to add the card content. Let me just refresh my local host again. Let's see what's going on here. There we go. So we have the transactions card here. Now inside of the card content here, we are first going to do the empty state. If data.length is 0, in that case, we're going to go ahead and we're going to show a div with a class name, flex flex call gap y4, items center, justify center. And we're going to fix the height to 350 pixels. And inside, we're going to add a file search icon from Lucid React. We're going to give it a class name of size 6 and text muted foreground. And we're going to add a paragraph here, no data for this period. And this paragraph is going to have text muted foreground and text small, like this. And then we're going to have the else clause here. And we're going to have our area variant here. Let's go ahead and let's pass in the data as the prop data here. So now we have to create the area variant option here. So let's go ahead inside of components and create area variant.psx. And inside of here, let's go ahead and let's import everything we need from the recharts library, which we have just added in our project. So tooltip x-axis, area chart, area responsive container, and carteson grid. And we're also going to need the date FNS format. So let's copy and paste the props from our chart, right? So they need to be the same here. We're accepting the same data, but this time the data here is required. And let's export const area variant here. Destructure the data, passing the props, and go ahead and return the following. Responsive container with a width of 100% and a height of 350 pixels. Add an area chart, which we've added an import for, and give it the data. As you can see, there are no errors because this data has the proper types for this. Add a Cartesian grid here and give it a stroke dash array of three space three. Then add depths here. So this will be some SVG elements, linear gradient. Give this linear gradient an ID of income. So it matches this, well, this value, right? income. It's going to have X1 of 0. So I'm not exactly sure what these values do. This is from the documentation of recharts, right? Y1 of 0, X2 again of 0. And then it's going to have a Y2 of 1 like this. Inside, we're going to have a stop element, which will be a self-closing tag. Offset here is going to be 2%. Stop color is going to be 3D, 8, 2, F, and 6. Stop opacity will be 0.8. We're going to copy and paste this. The lower one stop opacity is going to be 0. And the offset here will be 98%. Then we're going to copy and paste the linear gradient here. And this one will be for expenses. And these values, x1, y1, x2, and y2 will stay the same. The offset will stay the same here. But we're going to change to a different color. F4, 3, F5, E. And the opacity can stay the same. And then outside of defs, we're going to add the x-axis component. which we already have imported, give it an axis line of false, a tick line of false. This is going to represent the data key date from our option here. So this will represent the dates. We're going to have the tick formatter to get the value and we're going to format it. So this format comes from date FNS, right? so we're going to go ahead and format the value in day and then the month style is going to have font size of 12 pixels and tick margin is going to be 16 let's see if we can already uh render this so area variant from dot slash area variant or from components area variant let me just move this to the top here. So let's see, can we already maybe see something? We cannot, but we can at least see the grid and we can see our date range here at the bottom. So let's continue in the area variant here. So now what I want to add after the x-axis is the area itself. So type will be monotone here, data key will be income, stack ID will be income, stroke width will be two, stroke will be the blue color. So let's go ahead and give it here like this. Bill will be URL and then hashtag income. So just make sure you didn't misspell income anywhere here, right? All right. And class name will be drop shadow SM like this. Then copy and paste the area and paste it again. This time we are going to represent the expenses and use the red color from above. There we go. So you can see how this looks now. And now we have this kind of a weird look here because the expenses are shown negative like this. If you like this, that's fine. But we can change that by going back inside of our app API route summary and find the days. There we go, active days. and in the expenses, we're not just going to render transactions.amount. Instead, we're going to do then. Let me just zoom out so you can see then. And then we're going to wrap the whole thing in absolute. So we don't care if it's in negative, right? So just wrap this transactions.amount in absolute and save this. And let's see the difference now. And I refresh here. I think it's going to look a bit more pleasant. There we go, because now you can see the actual, you know, difference between the two. So you choose how you like it. Let's go back and set up the area variant here. And there's only one thing missing here. And that is the tooltip component. So let's go ahead and let's create the custom tooltip, which we are going to reuse. Customtooltip.tsx here. Let's go ahead and let's import the format function. Let's go ahead and let's import the format currency. And let's go ahead and go inside of our terminal. And let's do Bonex chat CNUI latest ad separator. And then let's go ahead and run our app again. Let's go ahead and let's import the separator component. and let's export custom tooltip here, active and payload. And it's simpler to just give this a type of any. And let's go ahead and check if it's not active, we are not going to display the tooltip at all. And now let's go ahead and let's extract the values from the payload here. So date, income and expenses. So the reason I know exact values which are going to be here are because of the documentation of recharts, right? So the tooltip works with this payload prop. You're going to see where we are going to render the tooltip later. And this is how you access the date. And this is how you access the income because it's the first in the array and then the expenses. And now let's go ahead and let's return a div with a class name. rounded sm bg white shadow sm border overflow hidden And let go ahead and let actually add the tooltip so we can see what we are doing So just after the x I going to add the tooltip component And we already have this imported right here. But now we're going to add our custom tooltip import right here at the bottom. So make sure you've added the export const custom tooltip in the components folder. And now what we're going to do is we're going to render that in the tooltip. so content like this so the props will be transferred now here so right now you're not seeing anything right but the tooltip does exist you're going to start seeing it the more we code here so let's go ahead and first add a div here with a format date and give it a format of months day oops day and then a year let's give this a class name text small padding of 2 px of 3 background muted and text muted foreground and then let's add a separator component then let's open up a div with a class name p2 px3 space y1 then open up a div again with a class name flex items center justify between and gap x4 and now inside of that add another div with a class name of size 1.5 bg blue 500 and a rounded pool. And this will actually be a self-closing tag. And next to it, you're going to add a paragraph income. And this will have a class name of text small and text muted foreground, like this. And then outside of this div, you're going to add a paragraph, which will format the currency income. And give this a class name of text small, text right, and font medium. like this, then copy and paste this exact div. Let me just see exactly what we have to copy and paste just a second. So I made a small mistake here. Inside of this div with flex item center, justify between and gap X4, add another div here. And that div will encapsulate this income here like this. And then we're ending this div here. There we go. So my mistake. Let's now indent all of this like that. All right. Let's do this and this and this and this and this, I think. and then this new div will have a class name of flex item center and gap x2 and let's just see if this is now working or not so if i refresh yeah make sure you refresh because we've shut down our server here so let's see the result now all right so we are seeing something but it looks like we have a small little bug here so let's go inside of the custom tooltip and i have a typo so bg white like this there we go let's see it now there we go now this looks fine and let's also add the expense if it exists so what we're going to do is we are going to copy this entire thing, which represents the income here. So let's add it and this will be the expenses of that day. This will be BG rows and this will use the expenses times minus one. So it shows them in a negative way. If you didn't change in the summary, if you use absolute, then you have to multiply it by minus one here. If you don't use absolute, then you don't have to do this. So when you take a look now, there we go. You can see your expenses and you can see your income in this beautiful tooltip. Perfect. So now what I want to do is I want to go back inside of my chart component and I want to create two more variants here. So first I want to create a bar variant. So let's go inside of components here. bar variant dot the SX. Let's add the imports we need. So similar to the one in ARIA variant. So we need these from recharts and we need format from date FNS. The props are exactly the same and we are also going to reuse our custom tool pit. So this is the basic setup. Now let's export const bar variant here. props, and let's destructure the data. Let's go ahead and let's return our responsive container with a width of 100% and a height of 350. Let's do bar chart here with data, data. And now we add the Cartesian grid again with a stroke dash array of three and three. And then we add an X axis here. And we can actually, I think, copy from area chart the exact props here, right? So we want the chart to look exactly the same. And then we also add the tooltip inside like this. So a tooltip with our custom tooltip. And then we have a bar. so bar will have a data key which will be income and the fill is going to be our blue hex color here and class name will be drop shadow sm then go ahead and copy and paste this bar and the other one will represent the expenses and we'll use our red color and that's it so a simpler chart here just make sure your income and expenses are not misspelled from here. And you can try it out now. So I'm going to add the bar variant here, passing the data to be data and comment out the area variant here and change this to use components. And there we go. You can already see it right here. This is how it looks like. And we still have the reusable component tool too. Great. And now I want to create the line variant. So let's go ahead and let's copy and paste the bar variant and let's rename it line variant here. Let me just find it, line variant. The props are exactly the same here, but we do need a line chart instead of bar chart. And instead of bar, we need a line. And I think that's the only change here. So inside of here, we are using the line chart for this. The x-axis stays the same. And now lines are going to be a little bit different. So this will be line. Data key is the same. And we're going to add dot pulse. And this is no longer fill. Instead, it is stroke. And we're going to have stroke width to be two. So let's go ahead and do the same changes here. This is a stroke. This is a line. Dot is going to be false. And we are missing the stroke width. There we go. And this is a line chart. So slight modification. And let's export this as line chart as well. Don't forget that actually not line chart. My apologies. Line variant or variant. Now let's go back inside of our chart. And let's go ahead and copy and paste this. And from line variant, we are importing line variant. There we go. Let's try that out. So I'm going to comment this out and I'm going to add the line variant with data, data. Let's see how that looks like. And there we go. This is our third line variant. And now we're going to create a component which will enable us to switch between these three. Let's go and stay inside of this chart component here. and let's go ahead and let's import useState from React. And then inside of here, I'm going to go ahead and define chartType and set chartType from useState. And by default, it's going to be area. Then let's add on typeChange here, typeString. And let's go ahead and say set chartType to be the type. and I'm going to add a to-do here, add paywall. So later, this is going to be a paywall. And now, let's go ahead and let's import everything we need from our component library. So I'm going to do that just above the card here. So we need select, select trigger, select content, select value, and select item from components UI select. Now that we have that, let's go ahead and let's create our select. right here where we've added a to-do next to the card title. So instead of here, I'm going to add the select component. I'm going to give it a default value of chart type. On value change is going to be on type change. And inside of it, we're going to use a select trigger, which will have a class name. LG with auto. Let me just scroll up a bit. Height is going to be nine, rounded, MD, and PX of three. And inside, we're going to use the select value, which is a self-closing tag, and give it a placeholder of chart type. Then we're going to add a select content here. And inside of here, we're going to add our select item. This select item will have a value of area. We're going to open up a div here with a class name, flex and items center. And each of our items is going to have a little icon. So let's add area chart.\nfrom Lucid React. And let's go ahead and give it a class name, size four, margin right of two, and shrink of zero. And let's go ahead and give it a paragraph area chart. And let's give this a class name of line clamp one, like this. There we go. So now you can see that we have a cool option of area chart right here. Great. what i want to do now is i just want to kind of separate this chevron down from my main content here because we now have this little icon here so we can do that by going directly inside of the select trigger component so that is inside of our components folder ui our select component so not the react select which is outside of the ui folder make sure you are inside of the ui folder and find the select trigger here and find the chevron down and simply add a margin left of two here and then that's going to look a bit nicer all right let's see that There we go. That looks better. Now let's go ahead and let's add the rest of the items here. So I'm going to go ahead and copy and paste the select item. This one is going to be for a line and it's going to say line chart and it's going to use the line chart from Lucid React. And the last one is going to be the bar. So let's copy and paste the select item. This will be bar option and this will be bar chart three from Lucid React. And it's going to say bar chart like this. Great. And now what we can do is we can modify this. So exactly the same as it was, but we're going to add a fragment here. And we're going to add conditionals. So if chart type is line, we're going to render the line variant with the data. Let me see if I have too many spaces here. I think I do. If our chart type is area, we're going to render the area variant. If our chart type is bar, we're going to render the bar variant. So let's go ahead and check this out. Right now, it's area chart. If I click on line, it will become a line. If I click on bar, it will become a bar. Amazing, amazing job. Now that we have the transactions chart, let's create our category spending chart. And it's going to be in form of a pie, actually. So let's copy and paste the chart. And let's go ahead and rename this to spending pie. Let me just find where it is. There we go, spendingPi. And let's change this to spendingPi as well. Now we have to go back inside of our data charts component. So inside of here, we render the chart. And now what we're going to do is we're going to add a new div here and add the spendingPi like this. Let's give this a class name of call span one, LG call span three, Excel call span two, like that. And the data that we are going to pass will be data question mark categories like this. So right now, this is not exactly going to work because we are using the variance from the chart. So let's go inside of the spending by and let's modify everything we need one by one. Starting with the types. So the type inside of here is gonna be different. So the previous one had date, income, and expenses. The new one will have just name and value of the category. Now let's go ahead and let's modify the default value of the chart type to be pi, like this. And let's go ahead and let's add the icons we are going to need here. So I'm going to remove this stuff. We are only going to need a file search. Everything else can be removed. So we're going to have a pie chart, a radar, and a target. Those are the icons we are going to use. So in the first select item, we are going to have pie and a pie chart. And this will say pie chart icon. Then in the second one, we're going to have a radar, and this will be a radar chart. Actually, I think it's a radar icon. Yes, so just radar chart here. And the last one will be radio, and this will be our target icon, and this will be a radio chart right here. Now let's go ahead and let's just modify the title here. So this will say categories like this. There we go. and this for like for empty data inside can stay exactly the same. I don't think we have to change anything here and now let's go ahead and let's change this to be pi, let's change this to be radar and let's change this to be radial like this and now instead of having a line variant And we're going to go ahead and we're going to have a pi variant. So let's go ahead and let's create that. So not in the UI, but in the components, create a pi variant dot dsx. Let's import everything we need from recharts right here. And let's also add our format percentage icon here. Then let's define a constant with our colors. So each of our categories are going to have a different color here. And remember, we only map for categories. Everything else is other, right? And we copy and paste the props from our spending pi. So it matches this structure right here. Let's go ahead and let's export const pi variant here and destructure the data, which matches the props. And my apologies for my pronunciation of variant and variant. I know I keep mixing it all the time. Let's go ahead and let's return the responsive container. We're going to give it a width of 100% and a height of 350. And now I'm going to add the pie chart inside and let's go ahead and let's add the legend like this. And now what I want to do is go inside of pie chart and render the pie. It's not going to be a self-closing text. It's going to be like this. And we're going to go ahead and give it some props. So data will be data. CX will be 50%. CY will be 50%. So that's the positioning. Outer radius will be 90. Inner radius will be 60. Padding angle will be 2. and fill, it's going to be this color right here. Data key will be value and label line will be pulse. And inside of the pie, we are going to iterate over data using data.map. We are going to skip over the entry and just focus on the index and we're going to render a self-closing cell component which will have a key which will be cell dash index and fill will be colors index mod colors length so we get a random color like this there we go let's go ahead and try this out now so I'm going to go back to the spending pi and I'm going to import the pi variant from dot slash pi variant right here. Let me change this to components and remove the line variant because we don't need it here at all. Components pi variant looks like I don't have that pi variant right here. Did I forget something? Expert const pi variant components. A typo here. All right. Let's try it out. And there we go. So rent, utilities, this clothing other, perfect. But now I want this to improve the label and to add a tool tip. So for that, let's go back inside of the legend component here. And we're going to go ahead and give it some props first. So we're going to give it a layout of horizontal, vertical align of bottom, align of right, and icon type of circle. So there we go. You can see the changes happening now. and now we're going to go ahead and manually modify the content inside. So go ahead and destructure the payload here. We're going to give it a type of any and we're going to go ahead and return an unordered list with a class name of flex flex call. Whoops. Flex flex call and space Y2. And then inside we're going to go over payload.map. We're going to get entry which is any. and index, which is a number. We are going to immediately return a list element with a key, open backticks, item-index. So that's going to be the first attribute. And then we're going to have a class name for this list, flex items center. Let me scroll up so you can see. And it's going to have space X of two. Inside of the list, we're going to have a span element, which will very simply be a self-closing tag, in fact. And it's going to have a class name of padding2, my apologies, size2 and rounded pool. And then it's gonna have a style, background color, entry.color. So whenever you're doing dynamic colors, do it inside of style. Don't do it inside of class name here because Tailwind has a just-in-time compiler. So stuff like this won't work. And now let's go ahead. Actually, that's it for this span. Now we open up a div and we write a class name here, space X1. And we write another span in which we render entry dot value. And we give this class name a text small and text muted foreground. And then we have another span where we render the format percentage, which we've added an import for in the beginning. Entry.payload.percent times 100, like this. And let give it the class name of text small And I think this will now be an improved component right here Let refresh to take a look There we go. So this now looks better like that. And it looks like this on mobile. Now let's create a custom tooltip here. So we're going to go ahead and copy the custom tooltip. And we're going to rename this to category tooltip. so it's going to be very similar we're going to rename this to category tooltick right here the same logic is going to be here but the values are going to be a bit different so we're just going to have the name from payload zero payload dot name and then we're going to have value payload zero dot value in this first argument here we're going to render the name and then inside of here, we're just going to render the expenses, right? So you can remove this first div and just leave the expenses and you're just going to render the value times minus one like this and then go back inside of the pi variant and let's go ahead and let's add a tooltip component with content category tooltip from dot slash category tooltip. Let's try it out now. So when I hover, there we go. You can see nice expenses which happened here. Perfect. Now we have to add more variants for our spending pie right here because these two are not compatible. So let's go ahead and let's create inside of components radar variant. This one is going to be very simple. So add these imports from recharts and go ahead and give it the same data structure. And then inside of here, we export const radar variant with the structure, the data. Inside of here, we are very simply going to return the usual responsive container here. Like we do. And inside, we're going to render the radar chart component. Let's go ahead and let's give it a CX of 50%, a CY of 50%, an outer radius of 60%, and a data of data. Inside, we're going to render a polar grid, a polar angle axis, which is going to have a style of font size 12 pixels and a data key of name of the category. And then we're going to have a polar radius axis, which is just going to have the font styles. and lastly we're going to have a radar itself with the data key of value stroke is going to be our usual blue color same with fill and we're going to have a fill opacity 0.6 like this so this is our radar chart quite simple Let's go back inside of this pending by and let's use the radar chart. Sorry, radar variant from dot slash radar variant right here. There we go. And let me just change this to use the components import. Great. And there is one more that we need here. So first of all, let's check out the radar variant. So when I switch to radar, there we go. This is how that looks like. Last one we need is the radial chart. so for that we can copy the pi variant so let's copy and paste that and let's call it radial variant let's go ahead and rename this to radial variant the colors can stay the same the data prop is exactly the same but instead of having cell and pi and pi chart here we're going to have the following. We're going to have a radial bar and we're going to have the radial bar chart. And now let's go ahead inside of here and let's use the radial bar chart. We're going to go ahead and give it the usual CX and CY props, except this one is going to be 30% simply because it's somehow weirdly positioned here. Let's go ahead and let's give it a bar size of 10. Let's give it an inner radius of 90 and an outer radius of 40%. And for the data, we're going to go ahead and iterate over the items. So data.map, we get the item, the index, and this is how we're going to fill it because that's the only way you can do it because you don't have the individual cell components as you do inside of your usual pi variant, right? So we don't have pi here. So we can remove this entire thing and inside of here it's the radar radial bar chart which is ending so let's go ahead inside of the radial bar chart just above our legend here and let's add the radial bar which is a self-closing tag and inside of here we're going to go ahead and style the label so it fits our font size our white color and the position will be inside start which is a specific thing for this radial bar. And we're going to give you two more props. Background and data key will be value. So our value right here. And I don't know if we have to modify the legend at all. I think the legend can work exactly the same. Oh, except, yeah, we are not exactly going to work with percentages here. We're instead going to work with values. So this is what we're going to do. let's go ahead and change this from do not use format percentage but instead format currency like this so this will be format currency and this will use entry payload value like this and this will stay entry dot value let's see how this looks like let's go inside of the spending pie and add the radial variant. So let me just change this. And let's remove the area variant and the bar variant. So we just have the compatible ones. Great. Let's try them all out. So we have the pie, we have the radar and let's see the radial one. There we go. Right. So this is why I didn't want to add percentages because I think we have percentages written out inside of these right here. Or actually, no, these are the values. So if you want to, you can use percentages, but I kind of feel like you can visually see them here. I don't know. I think it's just fun to see different variables here. So however you want to, you can just copy what you have from the chart. And let's see if the tooltip is working. So the tooltip here is not working. So I think it's not supported at all. So go inside of the radial variant and remove the tooltip. I don't think we need it at all and remove the tooltip from here great so that wraps up our charts now we have to just add the loading variance so we have to go back inside of our spending pie and we have to create the spending pie loading so let's go ahead and let's import the skeleton from UISkeleton and let's import loader2 from Lucid React, right? So I copied and pasted this from my original code to save some time as we are getting close to the 12 hour mark here. So spending pi loading has a card which is exactly the same as our card right here, right? So I just copied and pasted from there. So you can pause the screen and create the spending pi loading element. Make sure you add an import for skeleton and for loader 2. So I added loader 2 and here is my skeleton. Great. So let's save this and now we also need to do that inside of our chart component. This is very very similar. It's actually exactly the same. So you can copy this, go inside of the chart and paste it here. Again, make sure you've imported the skeleton and the loader to from Lucid React. There we go. So again, I'm going to pause the screen. So you pause the screen and you can see exactly what I've written here. As I said, I think it's exactly the same. Yes, there are no differences, I think. great so now let's go ahead and let's go inside of our data charts right here which are rendering the spending pie and the chart and let's improve this loading right here so we're going to go ahead and we're going to copy these two here but inside instead of loading a chart we're going to load the chart loading here. And instead of loading the pie, we're going to add the spending pie loading here like this. So let's go ahead and change these two components, spending pie like this. And let's try this out now. When I go ahead and refresh this, there we go. We have these beautiful loading elements all throughout our page. Perfect. We have changeable graphs on all sides here. Amazing. What we're going to do next is we're going to build the filters so that we can finally modify the data by different dates and accounts. Great, great job. Now let's go ahead and let's create the filters. So we're going to revisit an old component of ours called header. So let's go, I think it's inside of components right here, header.psx. And just below the welcome message, we're going to add the filters like this. There we go. Now go inside of the components and create filters.tsx component. We're going to go ahead and export const filters. And let's go ahead and let's return a div with a class name, flex flex call large flex row items center gap Y2 LG gap Y0 and LG gap X2 And then add what going to be an account filter So for now it going to be an error right Let's go back inside of the header and let's just import filters from dot slash filters or in this case, from components filters like this. Now let's go ahead and let's develop our account filter. So I'm gonna create that in the components as well. Account filter. Let's mark it as use client. Let's export const account filter. Let's go ahead and let's import everything we need from the select component. So that's going to be select, select content, select item, select trigger, and select value as usual. And let's also go ahead and let's import our use get accounts here. So import use get accounts multiple from feature accounts, API use get accounts. Let's also prepare a couple of things here. So I need to add a package, bun add query dash string. Let's import QS from query string like this. and let's import from next navigation a couple of things. So we're going to need the use path name. We're going to need the use router and we're going to need the use search params. So all three of those. Now let's go ahead and let's start developing this. So we're going to go ahead and return a select component here. Let's go ahead and give it some props. So value for now is going to be empty. On value change will be an empty method. And disabled will be false. Now inside, we're going to create a select trigger. And let's go ahead and give it a specific class name. So LG with auto. Full width. Type is going to be nine. Rounded is going to be medium. PX is going to be three. font will be normal bg white 10 cover bg white 20 cover text white border none focus ring offset zero focus ring transparent Let's go ahead and add some more Outline none Text white and focus PG white 30 and transition There we go Now inside we're going to add a select value Which will have a placeholder Account Now let's add a select content here with the first item be a default select item, which will say all accounts with a value of all. And now what we have to do is we have to add the hook to load the accounts. So we already have that here. So let's use it. Use get accounts will be right here. Whoops. So use get accounts. Let's destructure the data. That's going to be the accounts. And let's add is loading accounts. Let's go ahead and do it like this. Let's go ahead and use the accounts here. So we're going to do that right here just below this item. We're going to go over data? Get the account. and we're going to add the individual select items. So account.name and key will be account.id and value will be account.id as well. And no, it's not data. It is accounts. My apologies, right? So account.map and then we get the individual account. There we go. Great. Let's go back inside of the filters and let's use the account filter. So I'm going to switch this to components account filter. Let's refresh our local host to see how this looks at the moment. Let's go ahead. And there we go. So this is it. Right now it says account. Looks like we have some clerk hydration error here. So I'm just going to refresh this. There we go. So in here we have all accounts. We have checking and we have savings here. Great. So now we're going to go ahead and improve this value a bit. So let's go ahead and let's try and get the default value. So we're going to do that using the use search params here. So let's go ahead and write const params, use search params. And then we're going to have const account ID be params.get account ID or all. So this all needs to match this value right here. So now when I refresh, I think that this should be this, but it's not. Oh, because we're not passing the value here. So let's go ahead and pass in the account ID as the value here. There we go. So now the default is all accounts, as you can see here. And let's make this select account. right so we should technically never see the placeholder right because uh we fall back to all here and we just pass the value so by default it's all accounts uh great so we have that and now we have to also get the from or an empty string and we also have to get to because we want to preserve those values right So now we have this. Let's go ahead and let's add a router from useRouter, which we have imported. And let's get the path name. Oops, path name from usePathName. So let's get all of those. And now we're going to create a method called constOnChange, which will get the new value, which is a string. And inside of here, we're going to define our query. account ID new value from and to. And then we're going to say if new value is equal to all, we're going to go ahead and do query.account ID and reset the entire thing. And let's go ahead and write const URL QS stringify URL. URL will be the path name query. and let's go ahead and add additional options here. Skip null will be true and skip empty string will be true as well like this. So it's important that we preserve the from and to here. And lastly, router.push URL. There we go. Let's go ahead and let's use this on change now. So I'm gonna add that here on change. And now I wanna go ahead and try this out. So it's going to be disabled if we are loading the accounts, right? So let's use is loading accounts and use that for the disabled value here. So what should happen now? In my transactions, let's see which account am I using. So I'm using the checking account, it seems everywhere. So if I go ahead, select savings, there we go. I have no results. If I go into the overview and try savings here, no data for this period, no data, zero, zero, zero. Perfect. If I go back to checkings, everything is working. And you can see my URL change using the account ID. So all of that is perfectly transmitted inside of all of those hooks that I have for my transactions. So use get transactions, for example, uses those account ID params from my URL, which we now push right here. Right? We align it. And we did the same thing in use get summary, for example. Account ID. Right? So let's go ahead now and let's do one more thing. And that is to go inside of the account filter. And we're going to add one more hook here called use get summary. And the reason I want that is because every time we change the account filter, the summary will reload. So I want to keep track of that. So I'm going to go ahead and add const use get summary here. and I'm going to add, I'm going to extract the is loading, is loading summary like this, and I'm going to ensure that my select is disabled if I'm loading the summary or if I'm loading the accounts. There we go. Perfect. So now I want to go ahead and I want to develop the date filter, which will be fairly similar. So let's go inside of the filters here and let's add the date filter component. So we don't have it yet, but we're going to create it in a moment. So let me just close all of these things. Date filter dot the SX. Let's mark it as use client here. And let's go ahead and let's just export const date filter. And let's return a div. Now I can go back to my filters component and I can import the date filter. And let me just change my type of import here. Like this. And let's go ahead inside of here. And we're going to add a lot of similar things from our account filter, especially when it comes to the summary, the query string, and this kind of stuff, right? So we need the query string. we need all of these and we need use get summary right here. Besides that, we're also going to add a couple of things from React itself. So we need use state. From date FNS package, we need format and sub days. From React day picker, we need the date range. And we have React Date Picker because we installed ChatCNUI Calendar. And I also want to add an icon, just Chevron down. This is the icon I need Chevron down from Lucid React Great And we also going to add a couple of utils here So we need CN and format date range And we also need our button component. We need our reusable calendar component. And we also need everything from the popover. And the only thing we don't have here is the popover close. So let's go inside of components UI popover here. Let's add it. So const popover close will be popover primitive dot close. And what we have to do is we have to export it like this. And now there we go. No problems with popover close here. Perfect. Let's go ahead inside of the date filter component here. Let me just refresh my localhost to ensure that we see what we develop, right? So we're going to be using the popover component. So let's start with the popover. And now we are going to work with popover trigger here as a child. And we're going to use a button component here. Let's give it a couple of props. So disabled will be false. Size will be small. And variant will be outline. and now let's go ahead and let's give it a class name so I'm going to go ahead inside of my account filter and I'm just going to copy my class name right here let's go ahead and copy that and see if that's going to fit exactly what we need here looks like this fits quite well great and inside of this button, we're going to go ahead and write a span. And inside of here, we have to format the existing date range. So for that, we need the param state. So this is what we're going to do. We're going to go inside of the account filter, and we're just going to copy this, the params, right? And we can also copy the router and the path name. So let's do that. Add it to the date filter here. There we go. And now we're going to do const default to will be new date. And we're going to have const default from will be sub days as we usually do from default to and subtract 30 days. Great. And now what we can build is we can build a constant called parameter state. So if we have from, we're going to add new date. from otherwise it's going to be the default from if we have to we're going to use new date to otherwise default to like that there we go and now i want to go ahead and use that param state right here and we're gonna use the format date range and passing the param state inside. So there we go. Now you have default one from April 10th to May 10th. The same one we can see right here. It's the default, right, without any changes. Great. So we have that now. And now what I want to do is I want to introduce a state. So do we have use state? We already have the state. Great. So let's go ahead and do the following. Let's add const use state here. And inside of the state, we're going to keep the date. So let's add set date here. We need to keep it in the state because it's going to be a range. So we need to keep track of users' first input. So it can be this or undefined. And by default, it can be param state. and now let's go ahead and let's build our const push to url method so this will accept a date range which can be a date range or undefined let's create our query here which will have from to be format date range question mark from or default from. So it's going to use one of the two. And our usual format starting with the year, month, and then the day. Copy this and do the same thing for two. So it's going to go ahead and use the date range dot two, otherwise default two. and the exact same format goes here. Now inside of here, we're going to add const URL to be a QS query string. My apologies. Stringify URL. URL will be the path name. I believe we have added the path name when we copied it. There we go. We're going to pass in the query and let's add skip null. let's add both skip empty string and skip null to be true. There we go. And lastly, let's do router.push URL. There we go. And we're just going to create an on reset function here to reset the date and to push undefined to the URL. Great. So now what I want to do is alongside this param state for my data range, I want to add a chevron down. So we already have that imported. Let's give it a class name of margin left to size of four and opacity of 50. Like that. And let's go ahead and let's go outside of the popover trigger and let's add a popover content with a class name LG with auto, full width and padding of zero and a line of start. If you want to, you can also collapse these so they don't look like that. There we go. And then inside of here, we're going to use our calendar component. So let's go ahead and give it a couple of options here. So we're going to have disabled to be false for now. Let's give it the initial focus. Mode will be a range. So that's important because we're selecting two dates. Default month will be date from. Selected will be date. So this is from the state, right? On select, we'll simply call set date. And number of months shown are going to be two by default. And then below that, we're going to have a div with a class name. Adding for full with flex items center and gap x two. inside of that we're going to add a popover close which will have the as child property and use a button this button will have an on click which we'll call on reset and the reset will be disabled if we don't have date from or if we don't have date to And now let's add class name, pull with variant of outline, and let's write reset. There we go. Let's copy and paste this below. This one will say apply. OnClick will be a method which will call push to URL and push in the date. And the rest can say the same except the variant right here. Like that. So I think that should be it. And we also have the account ID. So let's use that here as well. So the query account ID will be account ID. Let's go. Let's try this out now. So I'm going to go ahead and select a much lower range now. For example, from 14th to 28th, click apply and take a look at my from and to. So it's from the 14th of April to the 28th of April. And it looks like I have no transactions in that period, or do I? Let's go ahead and try it from here. So from this to this, let's try. No results here. And I shouldn't have any there either. So let's go ahead and see if I have any in the past. Can we do this? All right, so I'm going to go ahead and see whether we have any bugs here. already so we do have a bug and the bug is in the account id so our account id was copied and it looked like this but we should not have this all because this all is not handled here it's only handed in the account filter right here there we go so let's go ahead and let's try this out now so go ahead and remove everything so just go to localhost 3000 and go ahead and select something in the past or just some smaller amount, like seven to 13, like this, just a week. If you're using the seed script, you should definitely have that data. There we go. Look at this beautiful information. And you can do the same thing right here on these queries like this. There we go. So now it's only loading those in that. And you can also combine that with the checking and the savings here. Great. Amazing, amazing job. Well, you finished pretty much the entire application. Let's go ahead and see what we are going to do next. Now, let's go ahead and let's go through our comments to see what we have to resolve. So, I'm going to go ahead and search for to do. So, let's see exactly all the things we have inside of here. So, I'm primarily interested in these ones. So I found out that you can safely invalidate transactions and it doesn't matter what the additional keys are. So yeah, we can leave this as it is. Let's go ahead inside of here and let's add the invalidation of the summary because we now have that. So summary and we can remove the comment or do delete transaction as well. Let's add the summary. Let's see what else. create transaction definitely let's add the summary so basically we want to refresh the dashboard right and of course for bulk delete let's see where else so edit category also needs to refresh the summary because we\nhave the category pie. And use get summary, this is fine. Use bulk create transactions, so definitely also needs the summary. And use delete category also needs the summary. Let's see what else. Use bulk delete categories, same thing. We need to invalidate our new summary. So edit account, I don't know if that needs the summary as well. So I'm not going to do it here because I don't exactly see how this would... But you know what? Let's go ahead and add just in case. Let's add the summary invalidation here as well and use delete account. Same thing. Yeah, actually it does make sense because if we delete the account, also the transactions will get deleted. Great. So I think we resolved all of the to-dos in our hooks here. Great. So you can go ahead and play along with the rest of the stuff here And if you find any bugs you can try and resolve them yourself Great great job Now let's go ahead and let's deploy this to Vercel to see if we have anything missing or anything that won't work. So I'm going to go inside of the terminal here. And I'm going to go ahead and do the following. So I'm going to do git add, git commit, and I'm going to go ahead and write any message here. So for example, deployment. And now what I'm going to do is I'm going to create a new repository. So for example, I'm going to call this finance tutorial. And let's go ahead and create this repository. So since this is an existing repository, we copy the second snippet here. So let's go ahead and push that here. There we go. And what I want to do now is first of all, refresh this page right here to confirm that everything is inside. Great. And then what I want to do is the following. I want to go inside of my package JSON and I want to build I want to run the build command So bun run build The reason I want to do that is so I see if there are any type errors inside of our project If there are we won be able to deploy the Vercel because it going to break the build So let's see if this will succeed or not. I'm going to go ahead and pause. And it looks like everything is just fine. If you have any errors, go ahead and resolve that first. Great. So now that we have that, what we're going to do is we're going to prepare our .environment.local file right here. And we're going to go inside of Vercel. Let's go ahead and select our new repository right here. And let's go ahead and add the environment variables. So you can just copy and paste everything inside of here and paste it like this. There we go. And go ahead and click deploy. And we're going to have to change the next public app URL. but we're going to do that after we deploy. We can only do it after we deploy because we don't know what Vercel is going to assign our domain name to. So I'm going to pause the screen and wait for the result of the deployment. And there we go. It looks like this is deployed. So now I'm going to go ahead and click continue to dashboard here because I need to get my domain name So finance and now we have to go back inside of the settings environment variables and we have to find the next public app URL And we have to edit it like this. But let's just keep the HTTP. But in our case, it's going to be HTTPS like this. And don't add the backslash. So HTTPS finance-tutorial.vrcelle. And let's go ahead and save this. And now that you've saved an environment variable, you have to go inside of the deployment, select the last one and click redeploy. And confirm one more time. I'm going to pause the screen again and tell you the results. And there we go. Our app is deployed and it fully functional. Great, great job. If you're interested in an extended version of this course, where we learn how to implement a monthly subscription and a paid plan using Lemon Squeezy, as well as how to connect to a bank account to fetch transactions using Blade, make sure to check out the link in the description and join CodeWithAntonio Pro to unlock the source code and all additional content for all of my projects.",
  "transcript_chars": 522455,
  "transcript_filled_at": "2026-06-06T15:59:14.855915+00:00",
  "transcript_filled_by": "tk-bulk-groq-retry-20260606"
}