In this chapter, we're going to create the layout for our dashboard part of the application. So let's go ahead and start by improving our file structure, and then let's add the layout files, which are going to render the sidebar, which we are going to use as our main navigation towards many various routes. So let's go ahead first and see if our apps are running. So just a quick reminder, I am using npm run dev all which uses mprox and the config file to simultaneously run ingest and next. If for whatever reason that's not working for you, you can always individually run npm run dev and npx ingest cli latest and dev that's the command even though for this exact chapter we only actually need this so I will do this simply because I prefer it this way.
And let's go ahead and go to localhost. And now let's go ahead and improve the structure of our files here. And sometimes you might see changes like this happen. It's my mprox. Now let's go ahead and go inside of the source app.
And in here, let's modify this a bit. Besides auth, I'm also going to have dashboard like this, another route group. So this will not be a part of the URL, it's just going to be used for structure. And now inside of here let's create another folder, one called editor and the other rest, as in the rest of the routes. We can think of a better organizational name later.
The reason I'm separating editor is because it's going to have a very specific layout. So that's why I'm having a separate organizational folder for it. And I'm going to keep the rest of the routes which are exactly the same and share the same layout in the rest folder. So let's go ahead and start by adding some things here. So let's add workflows which will be one of our entities and let's add page.tsx.
Let's do const page, return paragraph workflow page and let's do export default page and we can actually keep this simpler workflows as simple as that. And now that we have that ready, let's go ahead and let's remove page.tsx entirely. So remove it so that this breaks. And when you do that, you can also remove logout.tsx. We're not going to need that either.
So inside of your app folder you should now have global error which was added by Sentry and you should have layout file. Let's focus on the dashboard for now. Inside of REST we now have workflows and page.tsx and that is actually going to be our main route. So in order to make that the default route, let's head to next.config.ts and inside of next.config let's go ahead and do an asynchronous redirects function and in here let's return an array. Inside of this array I'm going to open an object with a source of forward slash as in the root page and change the destination to be workflows and set the permanent to false.
The reason I'm telling you to set this to false is if you have any other Next.js projects on your machine setting permanent to true will make them redirect the root page to workflows as well. So for that reason it's better to put this to false. It will work exactly the same and it will help you in development mode. After you do that, go ahead and restart Next.js. So use the letter R to restart it and refresh localhost 3000.
And you can see how that is immediately redirecting me to forward slash workflows. So I am now immediately redirected to here in case you can't see. Right? So if I try and delete the workflows route, again I'm redirected back here. That is exactly the behavior we intended to have.
Now let's continue developing here. Now inside of my app folder, dashboard, rest, I'm going to copy workflows and I'm going to make this credentials. You can select yes if it asks you to update the imports for credentials. It is basically going to modify the .next cache file. It looks kind of buggy but you don't have to worry too much about this file.
You can see it's unsaved. I can just click save and close it and I can just collapse the .next folder It really doesn't matter because it will be rebuilt each time we restart the server So inside of credentials page change these two credentials And now let's copy that and paste it here and change this to executions. And same thing for imports update here. It's a little bit buggy you can just go inside of the unsaved file, save it, close it and collapse the .next folder so you don't accidentally develop anything in there. Go inside of the executions and change the paragraph to executions.
So now you should have three routes. Credentials, executions and workflows. With workflows being your default route. So now that we have that, let's create individual routes here. So for example inside of here let's add credential ID like so and inside page.tsx.
I'm going to copy the existing page content, paste it here and this will be credential ID. And in order to make this fun, let's create an interface, page props, params, promise, and inside of here add credential ID to be a type of string. Now in here you can assign the page props. You can extract the params, make this an asynchronous method and simply the structure credential ID from await params. And then go ahead and render the credential ID.
So be careful about two things. First, make sure you develop this folder inside of the credentials folder. Second, make sure that you've added the square brackets. This makes it dynamic. And third, make sure you use the proper capitalization.
Credential is spelled in lowercase and id is spelled with a capital I. So credential id like this. If you named this credential id 2 for example, then inside of the page props this will no longer work. It has to be credential id here as well. That's why I'm telling you that capitalization is important.
So make sure that you have named this credential ID with a capital letter I. It's a little bit problematic here because of this font, they look very similar. And again this validator is showing up in the types you can just save it overwrite it it really doesn't matter you can even delete the entire .next folder and just restart the server and it will fix itself don't worry about it So how do you test if you did this correctly? Well, go ahead and go to HTTP localhost 3000 credentials 123. So I'm going to copy this route and I'm going to change the URL to that and there we go credential ID is 123 and if you're wondering why does this need to be a promise it's simply the way Next.js params API works So if you remove asynchronous and just do this and if you didn't type it as a promise I think it will still work.
Yeah you can see it still works but you also get an error this route used params.credentialid. Params should be awaited before using the properties. Learn more right here. Dynamic APIs. So let me paste this here And here it is.
So dynamic APIs are asynchronous. You can see that if you do this, you get that warning. So because of that, you have to make it asynchronous and you have to await the params. Exactly what we were doing before. So I'm going to revert it to that.
Perfect. So that's how we're going to handle individual credential. Let's copy this route and let's paste it inside of executions and let's change it from credential ID to execution ID. Again you can update the import, save the validator, close it and collapse the .next folder. It's a little bit annoying.
Let's go instead of execution ID page and change this from credential ID to execution ID and change this to be execution ID. So now the same thing, HTTP localhost 3000 executions 123. So I'm going to change the URL to that now, paste it, and here we have it. Execution ID. So if you accidentally misspell this, you will not be able to see your id and that's how you know that you named something incorrectly.
It could be a typo in here or it could be a typo in here or maybe it could be the invalid structure. And for the workflow id we're going to do it a little bit differently. So that's why I kept all of these in the rest folder because they all share the same layout and all of this is fine. But workflows will only be rendered inside of rest here. But very specific workflow ID will be rendered in the editor route group.
Yes, you can do that. You can create workflows as a folder again here, but you cannot create a page.tsx inside of here again. That would be a conflict. But what you can do is just create its child route workflow id like this. Now both routes will work.
Workflows will be using the rest organizational folder and if we add a layout file here later, which we will do, it will share the rest layout. Whereas the workflow ID will be using the editor layout. That's why we are doing that separation. Inside of workflow ID, let's create page.tsx and let's copy it from one of the existing ID pages. And let me just remove this, we no longer need it.
Copy it, paste it in the new workflow ID and change this 3 to be workflow ID and change this to be workflow ID. Once you've saved that file, you can very easily just go to the root of your localhost because you will get redirected to workflows and then simply add forward slash 123 and you will see workflow ID 123 is working. So this is what you should go to. HTTP, ignore the fact that I copied HTTPS. It's the browser's mistake.
It's HTTP. Perfect, So now we have improved our file structure and now that we have that we are ready to actually build the proper layout for this. So we're gonna go inside of dashboard here and create a new file layout.tsx. This layout will be shared across both the editor and the rest. So let's go inside of layout to fix this error.
Const layout And let's return here sidebar provider from components UI sidebar, which we added when we initialized chat CNUI. So you should have the sidebar provider instead of your components UI sidebar. It uses the use is mobile. That's how you can recognize it. I didn't just randomly say that fact.
And now let's just quickly give the props here. Children react dot react node. Children and in here let's add sidebar inset from the same folder so sidebar inset and render the children inside and give this a class name background accent with a 20% opacity and now let's go ahead and let's add app sidebar here which is a self-closing tag and we don't yet have it. We have to develop it. So I'm going to close everything, go inside of source components and create app-sidebar.tsx.
I'm not going to create it inside of the ui folder. I like to reserve the ui folder for chat-cn components. My own are going to go here in the components folder so inside of here let's mark it as use client because it will often be rendered in server components. So from here let's go ahead and import all the icons which we are going to need. Maybe we are not going to use all of them immediately, but let's just import all of these.
From lucid react. Let's import image from next image. Let's import link from next link. Let's import use path name and use router from next navigation and now let's import everything we need from the component from the sidebar component. The sidebar itself, content, footer, group, group content, header, menu, menu button, and menu item.
Perfect. And now let's go ahead and let's define the menu items here. The menu items is going to be an array of objects. Each object will be a definition of a route. For example, we're going to have workflows and the items are going to be an array of routes.
So the first one will be workflows. Icon will be folder open. And the URL will be workflows. And I just want to change something. Since all of them are using the icon version, I want to use the icon version for this as well.
You can do both in Lucid React. So for each credit card, there is a credit card icon. So you can see if I remove this I don't get an error because both exist as exports but I really prefer having the icon at the end because if you import link you can see it's a duplicate identifier but if you have a practice of importing things as link icon it doesn't happen so that's why I prefer being consistent here so I'm going to change this to this and let's stop here so that we can actually develop the app sidebar and see the results export const app sidebar like this let's go ahead and return sidebar collapsible is going to be a type of icon. Let's add sidebar content and inside of here menu items dot map. Get an individual group and for each group let's go ahead and let's return a sidebar group element.
Give the key a group.title and inside of here let's add sidebar group content inside group.items.map. Get the individual item, sidebar menu item, with a key of item.title. Inside of here, use the sidebar menu button to finally render the element. The sidebar menu button will have a couple of attributes. For example, toolpip item.title.
Is active, which for now we can hardcode to false. We are going to make it dynamic later. Make it as child. As child prop will make sure that this element here becomes whatever we put as the first child inside thus the name as child this is useful for when we want the sidebar menu button to be a link because it is not valid to render an href element within a button So because of that this button will become the link element. And let's give it a class name gap x of 4, height of 10 and px of 4.
Now inside of here let's use the link element let's give it an href of item.url inside let's render item.icon which can be whatever we have defined here for example folder open icon it's a self-closing tag and it's just going to have a size 4 class name. And finally a span rendering the item title. In order to make this link faster we are going to prefetch like so. In production, this will make sure to prefetch the content of this href on page load. So when the user clicks on it, it will almost be as if it was already loaded, which it is.
It will act like it is cached. So now that we have that, we are ready to render it and see something. So I'm gonna go inside of my layout in app dashboard and let's import app sidebar and let's go ahead all the way here and maybe zoom out a bit make sure you zoom out otherwise you will not be able to see it but here it is the workflow so let me zoom out as much as I can because if I zoom out too much it collapses to mobile mode. So if I click workflows you can see it redirects me back to workflows. Perfect.
Now let's focus on creating the rest of the menu items here. So besides workflows we're going to have credentials so let's add credentials here and let's also have executions here like that so all of them inside of items and the title here can really be home because or maybe main like that like main menu items and now on desktop mode here we should have workflows we should have credentials and we should have executions and all routes should work because we just developed the structure for them. Now let's go ahead and make the app sidebar look a little bit better by adding the sidebar header. Let's add sidebar menu item. Let's add sidebar menu button.
Let's go ahead and give the sidebar menu button as child again and let's give it a class name gapx4, height 10, and PX of 4. Let's add a link in here and let's prefetch to forward slash workflows. Whoops. Forward slash workflows. Okay, I think something's wrong.
Link like this, href forward slash workflows. All right. And prefetch. And I think that's good. Let's add an image with a source of logos forward slash logo svg we should have already been using this you can see in alt layout so whatever you used for the logo in alt layout is the same you should use here.
The alt will be node base, the name of our app. Width will be 30. Height will be 30 as well. You can find your logo in the public folder. Here logos logo.svg.
Perfect. And below the image add a span with a text of node base the name of our app with a class name font semi-bold and text small Let's go ahead and see how this looks like. Already looking much better. And you can see that this acts as the home button. Perfect.
In fact, you can make the href go to forward slash. This way it's a true root page and just accidentally we also redirect to workflows. So nothing really changes. But I think this is more semantically correct that this leads to a root page and this leads to workflows. Those are the same because we redirect but I think you get the idea what I'm trying to explain here.
Perfect. Now that we have that let's go ahead and make sure that we can highlight what is the active route. Const router use router, const path name use path name and now let's go ahead down here in the menu items and in the sidebar menu button for the is active let's check if item.url is forward slash. In that case let's check if path name is also forward slash. Otherwise let's check if path name starts with item.url.
So now once you've added this you will see that each of these will be properly highlighted and selected. The reason that we need to do this little trick is because if you don't do it, you can get into a situation where all routes are considered active. So you have to make an exclusion for this specific route page. Even though this can't really happen with our app because we redirect to workflows. But if you ever want to change it it will happen to you so leave it like this it's better.
Great now that we have that let me just go ahead and see one thing I don't like is that inside of the sidebar group content none of these have any space between each other maybe I will focus on that later I want to go ahead and create the sidebar footer. So outside sidebar content create sidebar footer. Sidebar menu. And inside of here let's add sidebar menu item. Sidebar menu button.
Let's go ahead and add a tooltip here. Upgrade to Pro. Class name will be the same as we've had so far for our buttons. Gap x4, height 10 and px of 4. On click for now will just be an empty arrow function.
And inside we're going to render a star icon and a text upgrade to pro. And then let's go ahead and copy the sidebar menu item here. Paste it below and change the tool tip here to be billing portal. Let's actually do it like this. Change the text to billing portal.
Use the credit card icon. And I think everything else can stay the same. And there is one more thing missing here. So you can see now we have upgraded to pro and we have billing portal. And if you want to get rid of this next JS indicator here, you can do that by going inside of the config and turn the dev indicators to false.
And I would suggest restarting your next route by pressing the R letter which will reset it like this. And then restart your app. So what we have to do now is we have to do the sign out button. So let's copy this sidebar menu item entirely, paste it here, set the icon to be log out icon, change the text below to be sign out and change the tool tip to be sign out as well. And that will be the finished look of our sidebar.
So credentials, executions. Yeah, You can see how they are not so mashed up down here but they are mashed up up here so I was definitely right that something is off but okay we will definitely resolve that. Let's focus on actually creating the sign out method. So this is actually quite easy. All we have to do is we have to import AuthClient from libAuthClient and let's go down here to sign out and let's call AuthClient.signout open an object fetch options on success router.push forward slash login just like this and now when you go here and click on sign out you should get redirected to the login page.
Let me try again, sign out. Not working too well, and I think the fault might be... Okay. Now it's not working because I am already signed out. So yes, we can't really properly demonstrate this because none of our pages are redirecting at the moment.
So let's do the following. First I'm just going to debug a little. What's going on here. So we have sidebar group. We have sidebar group content.
We have sidebar menu item here. Sidebar menu button we have as child we have link yes not exactly sure why this is happening I think that I'm missing inside of sidebar group content we need sidebar menu. Yes and just wrap the iteration around the sidebar menu and indent it here And I think that that will fix it. Here we go. That looks much better.
Much more visually cleaner. And now let's go ahead and properly protect our routes to end this chapter. So let's start by going into workflows page.tsx right here in the rest. Let's go ahead and make this asynchronous and very simply let's await require auth. I already explained why I prefer doing this individually in each page rather than using a middleware.
I personally consider this and the middleware just a helper for user experience. The real authentication and security layer is in our data access layer, our TRPC protected route. Everything else here is so that the user doesn't see errors or weird flows like we just had, right? So that's why I don't want to teach to use the middleware because people start to use it to protect their API routes and I personally protect individual pages like this. You can see exactly what's going on.
Executions. Let's do the same thing. So we add this. We turn this into an asynchronous page and we import. And I think I can copy this and just paste it in credentials too.
Perfect. And then let's do the same for individual credential ID same thing in here it's even easier we can just await require auth from lib auth utils let's go ahead inside of execution ID and do the same thing require ALF from libalfutils and there is one place left and that is workflow ID which is located in the editor here. So let's make sure to add that here too. Require auth from libauth-utils. And I think that's all of our pages protected And now we should be able to demonstrate the logout in a better way.
So 1, 2, 3, 4, 5, 6, 7, 8 is my password here. I'm logging in. I am immediately redirected to workflows. Workflows is highlighted. I can visit credentials.
I can visit executions and if I click sign out there we go. I am redirected back to login. Amazing! So I believe this puts us in a much better position as opposed to previous state where we had to you know put a bunch of things in page.tsx and it was starting to get a little cluttered so the next steps here, let me just fix 1, 2, 3, 4, 5, 6, 7, 8, login. The next steps here would be to create a add new button in the workflow and to create a header, which we are going to use to enable this on mobile mode and I actually want to use this chapter to add the header as well simply because it is very very easy to do so.
So we're gonna go inside of the rest folder, app folder, dashboard, rest and in here create a layout .dsx. We can go ahead and copy the existing layout here and just paste it but remove the sidebar provider remove all of these instead you can just go ahead and add a fragment like so you can wrap the children around a main element give it a class name of flex1 and Instead of adding anything here, let's just do app header. And now let's quickly develop app header, which should be super simple. Inside of source components, let's develop app-header.tsx and let's paste it here. So import I mean paste it.
I pasted it. You build it right. So import sidebar trigger from components UI sidebar export const app header. We are using the simple header element and we give it a class name of flex height 14 shrink 0 items center gap 2 border bottom px4 and bg of background and in here we render the sidebar trigger. So now that we have the app header here make sure that you build it.
Again, I pasted it from my source code. We didn't copy it from anywhere. It was my mistake. And now let's just import the app header from here. So very simple header here.
And you can see already how it looks. So now on mobile you should be able to open the sidebar header. I mean the sidebar. Okay. Perfect.
So I think that's more than enough for this chapter. Very clean, very smooth. We protected our routes. We created a very nice layout. And yes, so now the rest folder has this layout where it has the app header, but the editor will have a very specific header of its own.
That's why I didn't put it inside of here. That's why we are separating it. And if you're wondering about a better name for this folder, maybe home will be better or main, I don't know. If you really dislike Rust, because I don't like it too much but it's the first thing that came to mind. Great.
I think this is more than enough for this chapter. So again I have 14 files. Maybe you have 13 if you don't have the mprox.log so it's when an error happens you get a new log file. So yeah don't worry about that. All right.
Now let's go ahead and see if that's all we intended to do. I'm pretty sure it was. We improved the file structure, we created placeholder routes, we created the sidebar layout. Perfect. So 09 sidebar layout.
I'm going to create a new branch, 09 sidebar layout and then I'm going to push that branch. So I'm going to stage all of my changes 09 sidebar layout, I'm going to commit those changes and I'm going to publish the branch. And once I publish the branch, I'm going to go ahead and I'm going to create a pull request as usual. Let's go ahead and review the changes and let's wrap up the chapter. And here we have the summary.
New features. We added new dashboard layout with header and collapsible sidebar navigation. We dedicated pages for workflow credentials executions including detailed views by ID. Root URL now redirects to forward slash workflows. All dashboard pages enforce authentication.
We did some refactoring when it comes to navigation. We moved sign out around. It basically refers to the fact that it was all in one page that the sx and then now it is in the sidebar as always a walkthrough file by file here and for the first time I don't think we really need to look at the diagram simply because there isn't really any business logic involved here. We know exactly what's going on, right? We already went over all of these outflows in the previous chapters so we don't have to focus on it too much and We were pretty good.
No actionable comments beside the typo that I did. Instead of upgrade I spelled up up gate or I don't know. I think I missed something. Oh, the tooltip is misspelled. All right.
Great. So I believe that marks the end of this chapter. I'm going to merge this pull request. You merge it as well. And after you did so go ahead and change back to the main branch and go ahead and synchronize your changes, click OK, and let me just go ahead and review everything.
I believe that marks the end, it sure does. Amazing, amazing job and see you in the next chapter.