So now that we've learned how HONO works, you can go ahead and leave the API route behind. 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 don't have to. 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 gonna 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 gonna 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 going 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.reactNode 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 four and a PY of eight. 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 gonna 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 you know a bit larger because when we go ahead and create our navigation, we're gonna have separate navigation for mobile and separate for desktop, right? 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, So let me just see, at one point, my header stops expanding, right? So that's what this does, max width screen. So we are only gonna handle the responsivity after a certain width of the monitor, right? So I don't wanna support responsivity if the monitor is wider than this, we're simply gonna 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 full 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 .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's go ahead and give it an image which we already used inside of our sign in and sign up components, logo.svg.
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 gonna be inside. The second element is the navigation element. So let's go ahead and create that. Inside of components let's create navigation.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 a div navigation and I don't need a semicolon here and I can now import navigation from dot slash navigation or components navigation. Great! Now instead of the navigation here let's go ahead and let's define all of 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 gonna have accounts. Whoops. Then we're gonna have categories.
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 wanna go ahead and I wanna 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 gonna 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 outer 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 desk 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 gonna be route.label. And now let's go ahead and let's add our hook.
So that's why I marked this as use client called use path name so inside of here I'm going to get the path name from use path name like that and I'm going to go ahead and call this is active if path name is identical to route.hrep So if you're confused about the use client connection with the hooks I'm gonna go back in a moment and explain this but let's just resolve this error here. So inside of components folder I'm gonna create a nav-button.tsx file right here. And inside 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 gonna 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 .slashnav 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 useClient, there we go. I get an error here. What does it say?
It says you're importing a component that needs usePathname. It only works in a client component but none of its parents are marked with useClient. So they are server components by default. So if you want to learn more about that you can follow the documentation on this error here where you will learn the fundamentals. 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.
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 in with interactivity and that also includes hooks. So whenever you need a hook in a component, you will mark that as useClient. Alright, 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 useClient? What does that mean? So if you want to useClient shouldn't exactly be explained as all right make this component interactive. UseClient 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 a use client. So I just wanted to quickly explain that. So now you might be thinking, alright 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 useClient.
All right, we're gonna come to that example at some point and I'm gonna explain it again. Now let's stop the confusion and let's do the following. In the header we don't need useClient, 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. 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's 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 .slash UI button or components UI button and inside of here we're gonna add a link component from next link we're gonna 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 lib utils 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 isActive then something otherwise something. We could do that right but you're gonna 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 libutils 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, isActive can be something, otherwise this. Right, so that's how we are 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 wanna give it a full width.
On large, I wanna give it an auto width. I wanna give it a justify between. I wanna give it a font normal. On hover, I wanna give it a background white Slash 20 meaning it's gonna 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 0. 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 gonna give it a background white with opacity of 10. Otherwise background is gonna 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 bunx shad-cn-ui-latest-add-sheet bunx-shad-cn-ui-latest-add 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 gonna, well, use a couple of times throughout the project so bun add is gonna 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 run 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 gonna 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 the useRouter from next slash router so next navigation all right and let's define isMobile to be useMedia if open up parentheses maxWidth 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 gonna see why we need this in a moment and that's because if we are on mobile we're gonna have a completely separate return happening here. So we're gonna use our sheet component here and we're going to control it using the isOpen boolean and onOpenChange is gonna call the setIsOpen. 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 and do 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 off zero and ring transparent Those two. Outline is going to be none. Text is going to be white.
Focus, BG 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 the 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 8-4 and width 4. One cool thing in the new Tailwind update is that this class name can now be replaced with size 4.
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, FlexColumn, gap Y of two, and padding top of six. So it gives a little space at 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're 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 pathname in that case it's gonna be secondary otherwise it's gonna be ghost and let's go ahead and give it this a e route.href and let's give it an onClick to be an arrow function which calls the onClick method and route.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 gonna 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, 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, full 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 gonna do that even in this 404 routes. Perfect, so that handles our navigation component right here. What we have to build now is the welcome message right here. And below that, we're gonna have some filters.
So before we wrap up the chapter I want to do that. I want to add the little logout button here and a welcome message here and we're gonna 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 wanna do is outside of this div, I wanna add a user button component.
And I wanna 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 Clark loading and Clark loaded components. So Clark loading and Clark loaded. If it is loaded, We're gonna 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.
Size 8, animate spin, 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 Loading this is fine. So outside of this div right here, let's add a welcome MSG like this. So I'm gonna 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-nextjs. 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 gonna 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 .slash welcome message. I'm gonna 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 y2 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, right? And for the paragraph, we're gonna give it a class name of text small, lg text base, and text is gonna 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 welcome 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 use 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 gonna 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!