All right, so what I want to work on now is of course this actual content here where we're going to be able to create new lists and also new cards. So in order to do that, first thing that I want to do is I want to go inside of our schema. So we're going to go ahead and create the list model and the card model. So let's go ahead and do the list first. Go ahead and write model list and the id which is going to be a string and ID and a default value of UUID.
Below that go ahead and give this list a title which is also a string and also go ahead and give it an order which is an integer. So an order is going to be what we will modify whenever we drag and drop into a different position. Right? And when we initially fetch the lists, we're going to do order by and then we're going to use this order field. Great.
And now let's create a relation with a specific board so a list needs to exist inside a specific board so board id is going to be a string and then board is going to be a type of board which is going to be a relation which is going to have fields which are board ID which reference to the ID and on delete we're going to cascade so let me go ahead and zoom out so you can see this in one line right here. So now that we created an ID, a relation with the board, we have to go back inside of the board and create a relation with the list there. So go back inside of this board here and very simply just add lists and go ahead and add an individual list and an array and there we go. And now we have to add at at index here for board ID to get rid of that warning. So this is it, This is the code without any warnings or errors.
So we created a relation with the boards using this board ID, referencing to the ID of the actual board. And when we delete a board, we instructed that this list is also to be deleted, cascading, great. And now we have to create a card model. So let's go ahead and do that. Just below, go ahead and write model card.
It's also gonna have an ID of string at ID and default year UID. Let's go ahead and give it a title which is a string, an order which is again an integer and let's give it a description which is a optional string and db text and let me just align this items like that. You don't have to do this of course, it doesn't matter, it just needs to be in one line. And now let's go ahead and create a relation with the list. So list ID is going to be a string and then we repeat exactly what we did above so list is a type of list and has a relation fields list ID references ID and on delete cascade And let me just zoom out so you can see.
So list at relation fields list ID, references ID and on delete cascade. And now we go back inside of here inside of the list and we add the relation with the cards very simply by adding card and an array and then we have to do the at at index list id here and there we go again this is the code with no warnings or errors, so pause the screen and confirm you have the same. Great and what I want to add now is just the created at and updated at fields. So let's go back inside of the list here and after the cards I want to add createdAt to be dateTime and have default value of now and updatedAt is going to be a dateTime as well and have at updatedAt. So just like that and we can copy and paste this inside of the card model as well after the list relation.
Great! So now that we have that we are ready to push them to our database So let's go ahead inside of the terminal here and shut down your application. And first, let's go ahead and let's actually clear our entire database. So npx prisma migrate reset. So this is going to remove all the existing boards that we have inside.
After that is done, go ahead and run mpxprisma db push like this, wait a couple of seconds for this to align and let's do npx prisma generate. Great and then npm run dev finally. Perfect. So again if you have a feeling like you did something wrong you can always visit my github. You can even find the exact commit where I wrote this if that's how you prefer it and you can double check that your code is correct.
Also, if for any reason your Prisma doesn't have a syntax like mine, like this is yellow, this is blue, you can go ahead inside of the extensions and install the Prisma extension even though I think the Visual Studio code will recommend that for you automatically. Perfect, now that we have our lists, let's go ahead and let's revisit our board page. But just before we do that, we have to refresh our localhost and this should now yield a 404 because I reset my entire database here. There we go. So this is a 404.
So I'm going to go to localhost 3000 and that's going to redirect me back to my organizations here. So let me just go ahead and create a new test organization, sorry a new test board and make sure you are in a board ID page like this. Now let's go back inside of the app folder, platform, dashboard. Let's go inside of the board, board ID and here in the page.vsx where we just rendered the board ID, I actually want to extract the individual ID of the board and using that I want to fetch all the lists it has. So let's go ahead and write an interface for board ID page props.
So we accept the params which is an object which has the board ID which is a string. And now let's go ahead and extract those here so board ID page props and we can safely extract the params and now let's mark this entire function as an asynchronous function and let's go ahead and let's import Auth from clark-nextjs and let's also import db from at slash lib db and now inside of here first thing I want to check if I have access to the current organization ID using the Auth Helper. If there is no organization ID, in that case, we are going to redirect using next slash navigation. So let me move that here. We're going to redirect the user to slash select organization select org for short otherwise let's go ahead and let's attempt to fetch all the lists this board has so const lists is going to be await db.list.
We now have it because we did npx prisma db push and npx prisma generate and now we can do db.list.findMany and in here let's write board. Board ID should be params.boardID and now to confirm that only the people inside of this organization can load this list we're also going to write if board itself, so the relation, so the board that this list was created in also has the matching organization id of the currently logged in user and let's also go ahead and write include and we're immediately going to fetch the cards and let's order the cards by their order ascending like that. So our cards also have the matching order property as our lists have. And last thing we have to do is well order the list themselves so again order ascending. Perhaps this order field is a bit confusingly named Maybe I should have named it position.
So you can just imagine it's named position if it's confusing you, right? Basically what it is meant to represent is the exact order, how it's gonna be displayed on the user's screen. And this order field is what's going to be modified every time we drag and drop the card or the list itself. For the list, it's actually going to be quite simple because for the drag and dropping the lists, we can only move the order. It's either going to be first or in between or last something like that but with cards we're also going to be able to drop to another list and then we're going to be able to modify this list id as well so it's going to be a bit more complicated so just ensure that you of course have all the necessary fields in your schema prisma for that.
If you do, none of this should throw an error for you and it should work just fine. We of course don't have any lists so this is just going to be empty but you know it's good enough for now. We can go ahead and style this div now and write a class name to be padding for w sorry h full and overflow x auto and inside we're going to go ahead and render the list container and this list container is going to be a component which we are going to create which is going to have the board ID which is brands.board ID and it's going to have the data which is the lists which we loaded. Now let's go ahead and let's quickly create this list container. So inside of this underscore components where we have the board navbar, board options and board title form create a new file list-container.dsx and what I want to do in here is just quickly mark this as use client, create an interface, list container props, get the data which is going to be list from Prisma client and word ID which is a string and now export const list container let's go ahead and assign the props list container props and let's extract the data and the board ID and inside let's just return a div list container great now go back to page.csx and now you can import that from slash underscore components list dash container and there we go now we have a text list container in here but something in my interface is actually incorrect here so what I want to create is a specific type So as you can see this list is actually a type of list with cards inside of them but in my list container I just define them as list and since we're gonna work with that type list with cards a lot of times in the project, I want to create a reusable type for it.
So let's go to the root of our project. Let me just expand my screen. Alright, let's go to the root of our project. I'm going to close everything and create a new file. Not in any folder, so let me just close everything.
So go to the root of your project and create a new file types.cs and in here let's go ahead and Let's import card and list from app Prisma client and let's export type list with cards to be a list and open an object, cards, card. And let's go ahead and let's export type card with list because we're gonna work with that as well to be a type of card and list list like that. And now let's head back inside of our ListContainer component which we have in AppPlatform.Dashboard.Board.UnderscoreComponents.ListContainer where we defined to be an array of list but it's actually going to be an array of list with cards from types like that and now we have a correct type here. Perfect! So now we are ready to create our first kind of item here which is going to be the list form.
So inside of this list container right here let's instead use an order list like that and then inside let's go ahead and let's add a little self-closing div which is just going to represent the empty space at the end of our X axis. So add a class name flex shrink 0 and width of 1. So this is gonna help when it comes to scrolling. When we have more items and our X overflow activates this is going to create a little bit of padding at that overflow rather than it being flush to the end of the screen and above that create a list form component which is just going to have a self-closing tag. Now let's go inside of this underscore components here and let's create a new file list-form.csx let's mark it as useClient and let's export const list form for now to just be a live same list form go back to the list container and import the list form from ./.listform and you should no longer be having any errors.
Let's just see what is going on here. Okay, so I just had to refresh and now it says list form. Now before we go on to creating list form, I want to create a reusable component called ListWrapper. So let's go ahead inside of underscore components and create a new file ListWrapper.csx In here go ahead and import Well, actually, let's create an interface ListWrapper.props and let's get the children to be react, react node. And let's export const list wrapper to accept those children.
So list of wrapper props, accept the children. And Let's go ahead and return a list element which will render the children and let's give it a class name of shrink0 hfullmw of 272 pixels and select dash none. All right. And now let's go back inside of the list form and let's go ahead and actually start styling this. So inside of the list form go ahead and import our list wrapper which we just created and go ahead and replace this divs with the list wrapper and now inside we're gonna go ahead and open up a form element let's go ahead and give this form element a class name of w-full padding of three rounded medium bg white space y4 and shadow medium and there we go you can already see a little box here.
Perfect. Now let's go inside of here and let's create a little button which is going to say, add a list. Inside of this button, let's go ahead and give it a class name of W-full, rounded, medium, BG white slash 80 Hover BG dash white slash 50 transition padding of three flex items center on dash medium and text the small and one thing that I actually want to do is I want to remove this form wrapper so we're gonna do that if only if we are editing right I got ahead of myself so it should actually just be a button inside of the list wrapper like that. Great and below above this add a list text let's add a little plus from Lucid React so make sure you import the plus and let's give it a class name of H-4W-4 and margin right of 2. Like that.
And there we go. Look at our nice little button here. And now when we click on it, this is going to transform into our form. So let's do that now. In order to do that, we of course have to add some states some refs so let's go ahead and prepare all of that here so we're gonna import useState we're gonna use ref and element ref from react so let's go ahead and prepare isEditing and setIsEditing to be useState and by default false Let's go ahead and let's prepare a form ref which is going to be use ref with element of form by default it is null.
Let's copy and paste this one and let's rename this one to be input ref and element ref of type input And I just like to separate my let's put refs first and then the use state Great. And now let's go ahead and let's just create a couple of handlers like we did for The editing of the board title here in our board navbar. So let's write const enable editing to be an arrow function which calls this set is editing to be true and then calls the set timeout and let's just do input ref question mark current actually we can directly access the current and then dot focus like that. And now let's write const disable editing to be another arrow function, which is very simply just going to set is editing to be false. Great.
And now I want to write const onKey down to have an event of keyboard event And if E.key is the escape button, we want to disable editing. So this is just one of the things we're going to do to kind of copy that feel of using Trello and feeling like it's actually Trello. We're not just going to visually copy it. I also want to copy all the neat trick it has. Like when you click on escape the entire thing closes, right?
So we're gonna keep track of details like those. And now we can actually use a little use event listener from use hooks if you remember which we installed in the beginning of the tutorial we needed it for use local storage so let's go ahead and use it even further right if we have it let's use this use event listener here to listen to key down and call the on key down. And let's also add use on click outside again from use hooks TS. So use on click outside is gonna listen to form ref and then it's just going to do disable editing if we click outside the form ref. Great and now let's go ahead and let's actually assign this button to do something so give it an on click option to call the enable editing like that.
And now let's go ahead and let's write if isEditing, in that case I want to go ahead and I want to create that form. So return again a list wrapper and inside let's render the form element. Let's give this form element a ref of form ref. Let's give it a class name of W-full padding of 3 rounded medium bg-white space y4 and shadow md like that inside the form let's write our form input component from add slash components form form input so make sure you import this and let me just move it here above like that great and now let's go inside this form input and let's give it a ref of input ref. Let's go ahead and give it an ID of title.
Class name is going to be text small px2 py1 h7 font medium H7 font medium border transparent hover border input focus border input and transition. So we're practically going to match what this button looks like. So I want to make a very smooth transition when it turns into an input. I don't want the UI to jump around too much. So that's why I'm kind of using these weird values, because when I developed this, I just did a bunch of trial and error you know with CSS to see what looks the best.
So if you're confused and you think oh well I would never guess this class name so yeah you wouldn't guess this class names This is what you would do in trial and error. That's what mostly styling is for me. So I just want to clear that up in case you think I somehow know this up front, I don't know. And now let's give it a placeholder here, enter list title, and let's give it, well, that's all we can give it for now I think. So let's go ahead and try.
If I click here, there we go. You can see how it turns into a nice little input. And when I click outside it goes back well it goes back to being a button perfect! So now I want to create a little button below it so let's go ahead and inside of this form well I actually want to do another thing I want to go ahead and create a hidden input here so a hidden value and let's go ahead and give it a value of params.board.id and I didn't add params so let's go ahead and let's get our params use params from next slash navigation make sure you import that and now let's go here and just get params.board.id like that and give it a name of board.id so this way we could have technically also fetched this inside of our onSubmit() function Or we can add it to an input so then we can work purely with form data. It's however you want it to be.
And now below this, but still inside of this form element, create a div with a class name of flex-items-center and gap-x 1. And go ahead and render the form submit from add slash components form form submit so make sure you add this import as well and inside of here write add list and below that add a button component which you can import from components UI button so just make sure you do that as well great where are we We are here and inside of this button render the X icon from Lucid React as I added right here next to my plus icon. Great and let's give this X a class name of H5 and W5 and let's give this button a class name of actually no class name but let's give it an onclick to be disable editing And let's go ahead and give it a size of small and a variant of ghost. And let me close this. Alright, and let's try that out now.
When I go here, when I click, there we go. You can see how it immediately focuses on the input we can click on add list or we can click here we can also kind of click outside and then it closes we can click the escape and it closes as well perfect So what we have to create now is an actual server action which is going to be called when we click submit. So let's go ahead and let's copy one of the existing actions which we have. So I'm going to actually close everything for now. Let's go inside of the actions folder and I want to copy the update board.
Let's go ahead and copy it and let's rename it to create list like this. And let's go inside of schema.cs first. So we're definitely going to require the title and instead of requiring an id we're going to require word id. So the title rules can actually stay the same. It's required that a list has a title and let's just put a minimum length of 3.
This of course doesn't matter. For example, I'm pretty sure all lists should allow lower than 3. For example, if you want your list to be named QA that's just two letters but you know if you want to test out validation you can leave it at three it's purely your choice and let's rename this to create list. Alright so just make sure you have validation for title and validation for board ID. So that's what we expect for the user to enter.
And now inside of types.ts, let's rename this from update board to create list. And let's use it here as our input type. Now go back inside of index.cs here and let's go ahead and slowly modify this. So first we have this schema error instead of update board it is create list and we have to use it all the way to the bottom here in create safe action. So change this to create list and rename the action to be create list as well.
And now let's go to the beginning of the action and see if we have to modify anything. So the validation can actually stay exactly the same. We don't need to modify that. And now from our data, we're actually going to get the title and board ID. And we're not going to be creating a new board.
We're going to be creating a new list. So let's prepare the let list instead. And then in the try block, we are assigning to that new list constant and calling awaitDBBoard.list and .create. This means we're not going to be using the where clause at all. Instead we're going to be passing the data only.
We're going to pass in the title. We're going to pass in the board ID. But we're actually missing a couple of stuff, right? Because I also have to pass in the order, which now I can only hardcode. So let's go ahead and do the following.
First thing I'm gonna do is attempt to fetch the board where this list is supposedly to be created. So const board await db board find unique where id is board id and organization id of the current user matches. And now if we don't have a board, in that case let's return an error board not found. So this will prevent any outsiders from trying to create a new list inside of your organization's board and well in general check if by the time you attempted to create this list maybe the board was deleted. So this is going to prevent that from happening.
Now that we have the board what we can do is we can attempt to fetch the last list inside of that board so that we can properly assign the newest order of this list. So let's write const lastList to be a wait dbList find first where boardId is boardId, orderBy is order as descending, my apologies, and select, whoa, we just need the order. So we just want to hear about what is the last list in our board. And then when we have our last list we can generate a constant new order to say if we have the last list in that case let's do lastList.order plus 1 otherwise 1 like that and then we can copy this new order and paste it inside of here. Perfect and now let's go inside of the error and instead of fail to update let's do fail to create and instead of revalidate path id let's use the board id which we extract from the data and let's return data which is a list and now yeah we actually have to modify our type because we are expecting a board, I believe.
So let's just revisit our types.cs. Yes. So let's go ahead and instead of importing board let's import list from Prisma Client and put list as the second argument in our action state. And now as you can see we have no errors inside of our safe action handler. Perfect!
So now let's go ahead and let's find our app platform dashboard board components list form and let's go ahead and let's import everything we need. So we're going to need use action from hooks use action and we're going to need create list from actions create list. So make sure you have use action and create list and let's go all the way to the top here and actually I don't want to do it here I want to do it after these disable editing right so I want them to be in the scope first And then let's extract execute and field errors from use action. Let's pass in create list. And now let's go ahead and let's actually add some existing callbacks, right?
So on success, we get the data and let's just do toast which we can import from Sonar so just make sure you do that I'm gonna move it to the top. Alright so we have that and let's do toast.success, open backticks and let's write list, open annotations and let's write listdata.title created. So we immediately show that we fetched and well have the name of the new list. And then let's do disable editing. And what I want to do then is also call a router.refresh.
So let's just go ahead and do const router to be used router from next navigation. So make sure you import use router from next navigation. And then in here, let's do router.refresh. So we refetch all of our server components. And on error, let's fetch the error and do toastError and the error itself.
Great and now that we have that let's go ahead and let's create our onSubmit function. So const onSubmit accepts the form data which is form data and in here we have the title which is form data get title as string and we also have the board ID because we placed it in this hidden input here. Right. And let's go ahead and let's do execute title and board ID. Like that.
And then let's use this on submit to be an action of this board right here and let's also go ahead and give this form input errors of field errors and did we extract the field errors? We did. So make sure you extract field errors from useAction. And let's try it out now. So I'm gonna refresh here.
And first let's try something too short. There we go. We have an error that title is too short. And now let's try something like this and there we go it says that list has been created. Perfect!
We cannot see it here because we don't do any iteration over any fields, right? But what we can do is we can visit our Prisma Studio. So let me do that. NPX Prisma Studio. That's gonna open it here.
And let's go ahead and see. And in my list collections, it looks like I have one. And as you can see, it has the correct title. It has the correct order since it's the first one and it has a relation with the board exactly as we want it. Perfect, great, great job.
So what we're going to do next, we're going to create a kind of loop over our lists and actually render them out here. Great, great job.