Alright, so regarding this little bug we have that when we type something short and then we go ahead and type something again and it works but the error doesn't reset I think I found the culprit to that. So it's actually inside of our use action hook right here. This is something that I did not notice during my initial development so this is what happens. We only update the field errors which is what is showing right here if we have result.fieldErrors But let's take a look at our lib createSafeAction here. As you can see here, we only return field errors if this was not successful.
Meaning that this setFieldErrors will not update if we fix our error and we don't return anything in here. So we can do that quite easily by just removing the if clause. So this is inside of the use action. So we are always going to update the filled errors regardless if they exist or not. So let's try that out now.
When I write something that's too short I get an error but when I write something that has the proper length I create a new record and the error is gone. Great. So I think that should work. We are of course gonna see later in the development if this is gonna create any problems but I think it should be just fine. So now it's time for us to actually clear this screen up so we can actually create you know the some information about the current organization here at the top and a list of our boards here as well as the button to create new boards.
So let's go ahead inside of our organization ID page so dashboard organization organization ID and in here we have page.dsx And we can now go ahead and we can remove this entire form. We can remove this entire fetching of the database and we can remove all of these imports. And let's also remove everything we don't need. So we don't need this form anymore, that was just for us to test things out. We don't need form delete anymore and we also don't need the individual board.tsx.
So inside of my organization ID you should have the underscore components page, sorry folder, the settings page, the layout file and page.tsx. So let's go ahead and modify this by giving it a W full and margin bottom of 20 and then inside we're going to render the info component which we don't yet have and let's go ahead and create it now. So inside of underscore components here create a new file info.dsx and let's go ahead and mark this as used client and let's create an interface info props and actually we're going to do that later when we implement subscription sorry for that so for now all I want you to do is export const info props and go ahead and simply return a div info actually export const info sorry all right go back to page.vsx and then you can import the info component from slash underscore components info and once you save you should see the text which says info inside. Now let's go ahead and let's import use organization from Clark Next.js and inside of here let's use that hook so use organization and let's go ahead and extract the current organization and is loaded and now let's go ahead and let's write if is not loaded we're just gonna go ahead and return a paragraph saying loading for now so there we go now when you refresh you have loading for a couple of seconds and then the text info and now let's go ahead inside this return function and give this div a class name of flex-items-center-and-gap-x4.
Go ahead and open up a new div with a class name of w60px, height of 60 pixels as well and a relative and inside we're going to render an image component from next slash image so make sure you have that imported here it is a self-closing tag so let's go ahead and give it a field property let's give it a source to be, oops, a source to be organization question mark image URL, let's give it an alt to be organization and let's go ahead and give it a class name of roundedMD and object-cover and to fix this error we can just put an exclamation point at the end and there we go now our info component shows a big image of our current organization you can see how it changes slightly when I change my organizations. Besides the image, we also have to render the name of the actual organization. So outside of this div, which wraps the image component, open up a new div and open up a paragraph and render organization question mark name. Let's give this paragraph a class name of font semi bold and text XL. Great.
And now go ahead and give this upper div a class name of space y1 and below the paragraph open up a div which is going to render the credit card icon from Lucid React so just make sure you add this credit card icon. Let's give this credit card icon a class name of H-3W-3 and margin right of 1 and just write a small text which says free. So this is later going to be dynamic when we implement subscriptions. And give this div a class name of flex-items-center, text-extra-small and text-muted-foreground. There we go.
So now we have a nice little info component here at the top and now let's go ahead and let's create a board list. Actually, just before we do that, I forgot that we have this loading state which we have to improve. So let's go to the bottom of this info component and let's add info.skeleton the function skeletonInfo and let's return a div with a class name of flex items center and gap x4 then open up a new div with the class name of W60px, height of 60px and relative inside add a skeleton component from add slash components UI skeleton so just make sure you add that and let's go ahead and give this skeleton a class name of WFull and HFull and absolute property. Outside of this div open up a new one with a class name of space y2 and inside add a new skeleton component with a class name of h-10 and w200px open up a new div with a class name of flex-items-center and inside we're going to render two skeletons. The first one is going to have a class name of the height h4, w h4, sorry h4 and w4 and margin right of 2 and the lower one is going to have a class name of h4 and a w of a hundred pixels and now let's go ahead back inside of this is loaded and instead of rendering this we're going to render info.skeleton so save that let's refresh our localhost and there we go, you can see how now we have a nice skeleton here representing the image, the title and these two little icons here great So what I want to do now is go back to our page where we render this info component and just below the info component go ahead and add a little separator.
We already have that and you can import it from add slash components UI separator. Again, just make sure you don't accidentally import it from Radix. And just go ahead and give it a class name of my4. Great, so now you should see a little line here. And below that for now let's just go ahead and add a little div with a class name of px2mdpx4 and then render the board list component which we are going to create just now.
So go ahead inside of your underscore components here and create a new file board list dot dsx and let's go ahead and add export const board list here. So this is also going to be a server component we're not going to turn it into a client one and just render board list for now. Go back to page and then you can import board list from add slash components board dash list and there we go we have a little board list here. So now let's go ahead and let's create the UI for that. So class name for this first div is going to be space y4.
Then we're going to go inside of here and we're going to open up a new div with a class name of flex items center font semi-bold, text large and font, sorry, text neutral 700. Let's go ahead and render the user 2 icon from Lucid React, so make sure you add that import, it's a self-closing tag and give it a class name of h-6w-6 and margin-right of 2 and write your boards. There we go. So now we have this little text here and then we're going to render the boards. We're going to render the boards inside of a grid so open up a div and let's turn it into a grid by adding the class name grid and then we're going to define the amount of columns it can have on each viewport.
So on mobile devices it's going to be grid calls 2, on small devices grid calls 3, On large devices grid calls 4 and gap is going to be 4. And I forgot to add a little space here so this should be joined LG grid calls for let me just zoom out for a second so you can see all of this in one line. So pause the video if you haven't. Great and now inside of here usually we would go ahead and fetch the boards here and then iterate over them but since we don't really have all the necessary information to render the boards the way I want to render them we're going to go ahead and just create the first item inside which is going to be the button to create a new board so go ahead and create a div give it a role of button and go ahead and create a class name with aspect-video, relative h-full, w-full, bg-muted, rounded, small, flex, flex-col, gap-y-1, items-center, justify-center, so everything inside is going to be in the middle, hover opacity-75 and transition. Great, you can see our little box right here and inside of this box go ahead and add a paragraph which is going to say create new board and let's give this paragraph a class name which is going to say text small.
Below that let's go ahead and add a little span element which is going to say for now 5 remaining. And let's give this span a class name of text extra small. So this is what it's going to look like. Great. And now what I want to create is a component which is going to be used as a hint, right?
So when we, we're going to have a little like question mark here. So when we hover, it's going to open a little popover and it's going to explain to the user it's going to open a tooltip not a popover which is going to explain to the user what does it mean that it says five remaining if you saw the demo you already know what that means but Let's go ahead and create that component now. In order to do that, first we have to install the tooltip component from ShadCN. So let's go inside of our terminal here and let's run npx ShadCN UI at latest add tooltip. Wait a second for this to install and after it's done, we're going to create a component called hint great, so it's right here.
Let me just zoom back in and let's go ahead and let's go inside of our components folder and create a new file hint.tsx. Let's go ahead and let's import everything we need from at slash components UI tooltip. So we're going to need the tooltip itself. We're going to need the tooltip content, the tooltip provider and the trigger. So tooltip content, tooltip provider and tooltip trigger.
Now let's create an interface. Int props, children, we are going to be React, ReactNode. Description is going to be a string. Side is going to be optional and the type of either left or right or top or bottom and we're going to have side offset which is an optional number. Great!
And now let's export const hint. Let's assign those props, so hint props. And let's go ahead and extract the children, the description, side and side offset. And let's go ahead and give the default value of side to be bottom and the default side offset to be zero. And now inside all we have to do is return a tooltip provider.
Inside add a tooltip itself. Let's give the tooltip a prop called delay duration 0. So I don't want any delay with opening the tooltip, right? I want it to instantly open when the user hovers on it. Let's add the tooltip trigger to be our children.
And outside of the trigger go ahead and write tooltip content. And render the description inside. And now let's go ahead and fill this content with some props. So side offset is going to be side offset. Side is going to be side and class name is going to be text extra small, max w, 220 pixels and break words.
Great, so we have our hint component finished and now we can head back inside of our board list component inside of our organization ID components right here. So go inside of the board list and let's go just below this span element where we wrote five remaining and add a hint component from add slash components hint. So let me just show you where I imported that here at the top so hint from add slash components hint and let's go ahead inside and let's render the help circle component from Lucid React so make sure you add that icon it is a self-closing tag and go ahead and just give it a class name of AbsoluteBottom-2 Right-2 h14px and w14px and then let's go ahead and give this hint a side offset of 40 and let's write a description. So you can open back this like this and inside I want to write three workspaces can have up to five open boards or unlimited boards upgrade this workspace. Like that.
And now as you can see we have a little help icon here and when I hover on it we have a useful information for the user so they know what this means. Perfect! So now let's actually turn this into a popover and then we're gonna go ahead and create the connection with the Unsplash and all the other things. So I'm going to create that inside of our components form folder. And before we can do that, we have to install the popover component.
I don't think we have it. We're just going to check now. So npx shad-cn-ui-at-latest-add-popover. Looks like we don't have it so it's installing popover. Great.
Once that is done, go back inside of your components folder here, inside of the form folder and create a new file form-popover.tsx So the reason I'm putting this form inside of the reusable components folder is because we're going to reuse it both in the navbar as you can see with this little plus icon here and also in this board list component so that's why I decided to put it here. So let's go ahead and let's mark it as use client and now let's go ahead and let's import everything we need from s slash components UI popover. So we're going to need the popover itself, the popover content and the popover trigger. Great. And now let's go ahead and let's import use action from hooks use action.
And let's import create board from actions create board so those server actions which we recently created right and let's go ahead and let's import form input which we have from slash form input and let's import form submit from .slash form submit. Great. And now what I want to do is I want to go ahead and just well render the most primitive version of this So export const form popover and it's going to be an arrow function so like this and let's go ahead and create an interface form popover props it's going to accept children which is react.reactnode it's going to accept a side which is either going to be left, right, top or bottom. Besides the side, we're also going to have the align optional prop, which can be start, center or end. And we're going to have an optional side offset as well as we did in our hint component.
Great, so let's go ahead and assign those props to this form popover. So form popover props, get the children, get this side, and let's give it a default value of bottom. The align can stay empty and side offset, let's give it the default value of zero. Great, so now let's go ahead and let's return a popover component we're just going to have the popover trigger we're going to give it a prop as child and inside we're going to render our children and then let's add the popover content let's go ahead and let's give it an align prop of align let's give it a class name of W80 and padding top of three, side of side and side offset of side offset. Now let's go ahead and create a div with a class name, text small, font medium, text center and text neutral 600 and padding bottom of 4.
And inside let's just render create board. Perfect. And Now what I want to do is I want to use this form popover already before we start adding this other stuff. So for that we have to head back inside of our board list which is in the platform dashboard organization, organization ID components, board list and I want to wrap this entire div which is our button right it's right this which says create new board I'm going to wrap that entire thing inside of the form popover which I just imported from s-components forms sorry form popover great so go ahead and wrap the entire thing inside of that. So that's going to serve as the trigger to open up the popover.
That's going to be our children, right? We accept the children and we pass it here to be a trigger which is then going to open this content here. Great and let's go ahead and just give it a side offset of 10 and a side of right. So let's try it out now. If I go ahead, I'm gonna refresh and I click here.
There we go. You can see a little popover here which says create board. Now I want to add a little close icon to it but we have a little issue here. So let's take a look back inside of our form popover. It looks like I've exhausted all imports inside of my popover right.
There is nothing else inside of here that's being exported. We have the popover, the popover trigger and the popover content. That seems to be it. Well, thankfully, because Chatsien offers us to enter direct components in here, we can go ahead and add it ourselves. So let's do that.
Find the UI folder and go inside of your newly installed popover.tsx so you can either find it in the folder or you can just use this form popover component which we are working in called command or control and click on it and then just click inside like this. So just make sure you're seeing this. And we can do it quite easily. So let's write const popover close to be popover primitive.close. And then we have to export it at the end so after popover content add popover close and that is it we just modified this component so it serves our needs so outside of this div which says create board go ahead and open the popover close component give it a prop as child and of course we have to import that so let's just import it here there we go popover close and inside of here add a button component so I just imported that from .dell slash ui button but I'm gonna change that to slash components all right and inside of the button component I'm going to render an X icon from Lucid React so just make sure you do this.
There we go. Import X from Lucid React. And now let's render that here. Let's give this X a class name of H-4 and W-4 and let's go ahead and give this button a class name of H-Auto, W-Auto, padding to absolute, top-2, right-2 and text-neutral 600 and let's also give it a variant of ghost. And let's take a look at our popover now.
So when I click here, there we go, I have a little close button here and it is working. Great. Now let's go ahead and add some input elements inside of this popover right here. So I'm gonna go back inside and inside of this popover content after the popover close let's go ahead and let's render the native HTML form element and inside create a div with a class name of space y4 and let's render our form input component which we imported at the beginning. Let's give it an id of title, let's give it a label of board title and let's give it a type of text.
So now when you click on your component right here you should have the board title visible. Perfect! Now besides the form input we're also going to need the form submit. So let's go ahead and give this form a class name of space y4 as well and then outside of this div which is wrapping this form input which is going to have some more elements inside so just so you don't get confused why we're even doing this and then render the form submit which I think we also have imported so form submit and form input and go ahead and say create inside and then create a class name with w-full here and now when you take a look there we go we have the board title and our create button here. So what I want to do now is I want to connect that to our actions so let's go ahead here at the top and let's include execute and field errors from our use action hook which we have imported passing the create board and let's also add some callbacks so on success I want to receive the data and I want to have the data logged in my console inspect element and same thing on error I want to get the error and I want to console log the error if it happens.
Perfect. So now let's go ahead and let's create a little onSubmit function here. So const onSubmit is going to work with formData, which is a type of formData. Go ahead and get the title, which is formdata.getTitleAsString and let's call the execute function and pass in the title. And now let's assign this onSubmit to be this form's action.
Great. Let's go ahead and refresh this. Let me open up my inspect element here. I'm going to create a test board and you can see it's loading and there we go. We have the new test right here.
Let's try the field errors now. Oh that's not working because we forgot to pass in the field errors so let's go ahead and let's actually use those field errors let's see yes we extracted them from use action but we're not using it anywhere So let's go inside of the form input here and get the errors and just pass them here. There we go, they immediately showed up. So let's refresh and try it again. There we go, we have a short title here.
Let's go ahead and try and fill it up and it goes away. Perfect! So now what I want to do is I want to add a toast component which is going to show up when we successfully create something. For that I'm going to be using Sonar because I think it's just a nice and slick component. So let's go ahead inside of our terminal and let's run and install Sonar.
Very simple, just like that. And then, first things first, let's go inside of our layout component. Right here in the app layout, right here. And just inside of here, actually we don't need to do it in the root layout. We actually only need it in the platform layout.
So this is where I actually want to do it, inside of this layout, which has the clerk provider. Let's go ahead and let's render our toaster component. I think it's from Sonar. So let's go import poster from Sonar. Yes, looks like that is it.
And now I want to go ahead and Well, I don't want to do anything else actually. I think this is just enough. So let's go back inside of our form popover. So that is located in components, form, form popover here. And let's go ahead and in here let's add toast from Sonar so make sure you add this toast import we just learned how to import toaster but this time we're importing the toast from Sonar and let's run toast.success and let's write board created and for the error let's write toast.error And we can actually log the exact error, which is going to come from our database, because we make sure that that is just a string, like not an object or something weird.
So let's try that out now. I'm going to refresh here. Test. There we go. You can see at the bottom I have board created here.
And now let's try and simulate an error here. So I'm going to go inside of actions, create board index and inside of this try I'm going to throw new error. Let's try that out now so again I'm going to refresh. I'm going to write test and there we go it says failed to create. Great!
So just make sure you remove this artificial error here and save all of your files. Perfect! So you're on a good track on creating this form popover. What we are going to do next is implement another form component which is going to be called form picker and that's going to stay above this board title and it's going to load nine random images from the unsplash API so you're going to learn how to do that Next. Great, great job.