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 drizzlezod.
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 font drizzle Zod like that. And then what you can do is write export const insert account schema create insert schema and simply pass in the accounts like that. There are of course more options than just this, Sometimes you will have to slightly modify the schema as to oppose what's in the database.
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 semi colon 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 gonna add the clerk middleware here and then I'm gonna 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's just see where can we import the z-validator from so let's 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 blade 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 gonna do it. So I will tell the frontend, 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 frontend 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 dot insert accounts dot 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 on to 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 uuidv4 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 cuidv2 terminal here, one 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 whoops So by default the select script or the select function will return the data 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 .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 very 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 gonna be unique you can just immediately destructure it like this and then this would be the first item in the array. Don't do that for the get route. I'm just demonstrating on the get route here. Let me close it. Get route stays unchanged.
We are only working on the post route here. 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 values 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 gonna see that in a moment. All right, so what I want us to do next is I want us to add form elements from chat-cn-ui. And we can do that by using a script here. So bonnex 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 chatcn has added so we have button form label and sheet and we also need to add one more thing bun x chatcn let me clear this so you can see, bunx-chatcn-ui-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 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.tsx and let's go ahead and simply create that component so let's import everything we're going to need from s 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 wanna 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 gonna add a form, but for now I just wanna 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 gonna be called sheetProvider.tsx.
And let's go ahead and do the following. So let's go ahead and mark this as useClient. And let's export cont sheetProvider here. And let's render inside of a fragment because we're gonna 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 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. And we also have a hydration error here. So all of that is fine. We're gonna 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 gonna go ahead and I'm going to import useMountedState from react-use.
So what we can do here is write const isMounted to be useMountedState. And then I'm gonna write if is not mounted return null and now I'm gonna explain to you how this works seems like we are still getting an error but we're gonna go ahead and resolve this later right so let me first explain to you what the useMountedState does. So useMountedState is the equivalent of this. Const isMounted, set isMounted, useState 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.
So why does this work for fixing hydration errors? Well, it works because this setIsMounted can only be called inside of useEffect and useEffect or the componentDidMount 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 where 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.ts 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 exportCount to 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 the 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 gonna change this to features accounts.
So I like to keep my imports like this. I'm gonna 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 to 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 gonna 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 gonna change this later. Obviously it's not gonna 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. 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 gonna go inside of app, my apologies, components, UI, sheet component. And let's go ahead and let's find the variance right here, sheet variance. So first thing I'm going to do, I'm going to find my default classes here and I'm simply going to add overflow Y auto.
So I want to make sure that people can scroll in these drawers. Then I'm going to find the left right here and I want to slightly modify it here. So instead of using the width 3 4ths here I'm going to use the width full and then I'm gonna modify this so it doesn't look at the small breakpoint but instead the large break point and I wanted to use max width 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 a 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? Alright, 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 width 3 quarters to width full and change this to use the LG breakpoint max width MD. Like this. And there we go. Now it has the effect that I want. So on tablet and mobile, I want it to behave full screen, but on desktops, I want it 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 gonna change, so in the left I'm gonna change this to 3 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 width SM to large max width 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 gonna 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 gonna need Z from Zod, we're gonna need the trash icon from Lucid React, we're gonna need useForm from React HookForm, ZodResolver from AddHookFormResolversZod, we're 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 gonna 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's define our form schema.
So const form schema is gonna be insert account schema dot pick, 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 gonna accept an optional ID in case we are editing an account. It's gonna have optional default values, which can be a type of form values. It's gonna have an onSubmit method, which accepts the values, which again are a type of form values, and it returns back a void. And it's gonna have an optional onDelete, 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 ZodResolver 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 console.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.handleSubmit and pass in, whoops, form. It is, yes, handleSubmit and pass in the handleSubmit method again. So this is from the form constant and this is our handleSubmit. So using this wrapper around our method ensures that we only trigger this once it is validated.
So no invalid fields are passed. And let's give this a class name of space y4 and space y4 and padding top of 4 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 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 gonna have form control, which will use the input. The input will be disabled if the global disabled prop is being passed and we're gonna 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 gonna 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 you 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 events 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 .slash account form or features components, features accounts components. There we go. So I'm gonna 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 gonna be save changes. Otherwise, it's gonna 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 gonna check well let's go ahead and let's 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 4 and margin right of 2 like this. And let's go ahead and give some props here. So type here is gonna be button.
Disabled is gonna be disabled. OnClick is gonna be handleDelete. LastName is gonna be fullWidth. Size is going to be icon. And variant is going to be outlined so my apologies size is not like 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 just a second so I can debug this.
Alright, so I'm gonna leave this for later. 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? No, 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 wanna do now is I wanna go ahead and I wanna pass in the actual onSubmit method here. So let's go ahead and use that.
Let's pass in the onSubmit and let's pass in the values like that. So we should have the onSubmit 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 gonna 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 two 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 gonna go ahead and open my, let's go ahead and do this. I'm gonna open my inspect element here and I'm gonna name this account test let's see what the errors are alright we are having some errors we're gonna 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't have default values here.
So how about I go ahead and do this? If I pass default values, name to 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 gonna go ahead and we're gonna create a new hook in our features. So let's go inside of features API and we're gonna call this useCreateAccount.ts 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 use query 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, typeof 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.accounts.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 bun, bun X, chat CN, UI, latest ad, toaster, sorry, Sonar. Let's do bun run dev 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 dot 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 wanna use mutate async.
But you don't have to, because for example, after this finishes, alongside this onSuccess here, I also wanna close the account sheet. So I can do that by simply adding an object and I'm going to continue with the onSuccess here and call the onClose just like that And now what we can also do is we can disable everything while it's mutating. So mutation.isPending, like this. Let's go ahead and try this out now. So I'm also gonna go ahead and open my database studio.
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 gonna go ahead and zoom out a bit on everything. So I'm gonna 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 going to do next.
Great, great job.