In this chapter, we'll build the complete dashboard experience, a collapsible sidebar with organization switcher, user profile, navigation links, and then we're going to build the homepage with a personalized greeting, animated wavy background, a text input panel, and some quick action cards. We're going to build this in the following order. We will start with a layout with sidebar shell. We will then populate the sidebar with navigation links. And finally, build a homepage with input and actions.
And this is how it's going to look like. In order to get started, make sure you have npm run dev running and that your app is running on localhost 3000. Make sure to refresh to confirm everything is working correctly. Let's go ahead and start by adding a logo to our project. So for the logo you can choose whatever image you want and you have to put it inside of public folder.
If you want to you can use the link on the screen to get access to my source code and you can go ahead and find logo.svg file which I'm going to add right here. If you want to find the logos to use for demo use cases, not for real instances, you can go ahead and use LogoIpsum or UntitledUI. Both of them have amazing resources for demo logos. Great. Once you have added your logo here, we are ready for the next step.
And that's going to be building the dashboard layout. So what I'm going to do is I'm going to go inside of Source, inside of App Folder, and I'm going to create a new folder dashboard. Make sure that you write this inside of parentheses. When something is written inside of parentheses in Next.js, it tells it to omit that from the URL. Which means that once I create page.tsx inside it will be the exact same thing as having page.tsx right here.
Which means I actually have to delete this one because they are essentially on the same URL level. So let's go ahead and delete this page.tsx. We no longer need it. And now let's go inside of this new page.tsx and let's go ahead and just add a simple default export dashboard page, which renders a div and the heading element, which simply says dashboard. As simple as that.
Great. And if you refresh the page, everything should be working as expected and you can see my URL is localhost 3000. So that proves what I just said that when you add a folder in parenthesis, it's simply used as a route group. Meaning that this folder is used for grouping things together. Our folder architecture.
It's not meant to affect the URL in any way. Whereas these other folders like test is directly a part of the URL. You can test that by going to forward slash test. And you can see that we now have this loaded. So let's now go back to localhost 3000 and just make sure you have dashboard here.
You can also remove this test page since we will not be needing it. It was just for demonstration purposes. Once we have that, let's go ahead inside of this and create layout.tsx. And inside of here, we're going to go ahead and import cookies from next headers. We're going to import sidebar inset and sidebar provider from components UI sidebar.
Let me go ahead and collapse so you can see more clearly. So components UI sidebar is a ChassisNUI component which we've obtained in the first chapter. And then let's go ahead and do a default export of dashboard layout. The dashboard layout needs to be a default export. Make sure it's an asynchronous function because we are going to have to await cookies inside and you will see what we'll use cookies for in a second.
And the only prop this function will accept are the children, so I simply map the types here to be children react react node. Great. Once we are in here, we can very quickly see some results. So I will just type layout and save this file and immediately you will see a different on our localhost 3000. So you might be wondering why did this layout file override the page file?
What's going on with that? Well, both page and layout are reserved file names in Next.js. Page is used to represent a route, whereas layout is used to represent a route or route groups layout. So why is it overriding this content? Well, because we are using it incorrectly.
We're not utilizing the children. So if you want to see what I'm talking about, let's go ahead and wrap this inside of a navbar and let's render the children below. And now you will see both layout and dashboard written here. So to show you what this layout file does, go ahead and create a new folder here, for example, Users. And a new page inside once again.
So your dashboard folder should have a page and users. And inside of users another page. So I'm gonna go ahead and just export default function users. Let me fix the typo here. I can't seem to fix the typo.
And in here I'm just gonna say h1 users and let me go ahead and just add text to Excel font bold so if I go ahead and go to local host 3000 forward slash users let me show you so again we don't write dashboard here right there's no need to write dashboard in the url because it's in parentheses meaning it's omitted and you can see what happens I still have the layout text above and users. So that's what the layout file is used for. You can put it inside of a folder or a route group folder like we are right now. And this layout will be preserved regardless of the route we are on, as long as that route is defined within a folder. So that's what the layout is for and it's a perfect solution to host or to render the sidebar.
This way, regardless if we are on dashboard, voices, text-to-speech or voice cloning, that will render in a page.tsx document here, but the layout will be unrendered. I mean, it won't re-render. It will stay rendered once and that's it. So that's the perfect place to put the sidebar in a layout file. And this is a page file.
I hope I cleared that up. Let's remove the users file for now, we don't need it and let's go back to our layout so you can now remove the navbar layout from here and you can go back to localhost 3000. All right so what we're going to do now is we're going to build our sidebar using the sidebar composition from Shazzy and UI. So I'm going to go ahead and render sidebar provider as the top argument. Default open for now can be set to true and class name here will be set to HSVH.
And let me fix the class name typo. All right. And now let's go ahead and add sidebar inset, which is another compositional component to make the sidebar content look good. Let's go ahead and give sidebar inset minimum height of 0 and minimum width of 0, which serves like some form of CSS reset, so we can have proper heights and overflows and scrolls within our pages which are rendered, well, here inside of sidebar inset. Okay, and to make this semantically correct, let's render children inside of a main HTML element.
Let's give the main HTML element. You can see how the moment you forget to add some class names, it definitely affects the entire page here. So let's, for example, this is now broken. We expect this dashboard to be fully centered, right? You can see using flex item center, justify center, that's what we expect.
That's why it's important to write proper composition. So we need to add another reset here, flex one and flex column. And once you save that, you can see that it's back to normal great now let's go ahead and utilize our cookies so let's define the cookie store by using await cookies and let's go ahead and extract default open to be cookieStore.get sidebarState ?.value equals true this is all in one line, let me expand so you can see like this, so basically this is a way to preserve the state of the sidebar because sidebar can be closed or opened So if the user keeps closing their sidebar and then refreshes, it gets quite annoying that they have to close it again. So this is a way to keep it in a state that the user last decided it should be in. So now let's use this variable here.
And if you're wondering where do we get this sidebar state, I'm pretty certain that if you command click inside here you will be able to find this sidebar cookie name sidebar state right here. So just confirm that it's the same. You can even use a constant here, but the last time I exported this and attempted to use a constant, it actually caused some hot reload problems. So That's why I'm writing a magic string here. And we need to do optional operator here because the cookie doesn't have to exist.
All right, so we now have the provider, we have the inset, but we actually have no sidebar yet. So that's what we're gonna do next. And I'm just gonna prepare the import for that. So I'm going to go ahead and add that right here. So dashboard sidebar will come from, I'm not going to do it yet because you have no idea where I'm importing this from.
So let's actually build it first so you can see exactly where we're going to import this from. Sorry for that. So inside of source, let's go ahead and create a new folder called Features. And this is why I told you source folder is important, because of the way we're going to architecture our app. So I want the features to be inside of the source.
It just wouldn't feel the same if features folder was on the same level as Prisma and Publix. So that's why I want it in the source folder. So we're gonna be using a features split architecture here. So I'm going to create an entity or a feature called dashboard. And then in here I'm going to create components.
And then in here dashboard-sidebar.tsx. Let's go ahead and mark this as UseClient. And let's immediately import a hook from Next Navigation because we couldn't be able to do this if we didn't mark it as UseClient. Because if a component is not use client, it's a react server component and those are essentially API routes just in a JSX way. It's not exactly the proper definition.
I'm just using that definition in case you've never heard of them before, so it's easier for you to understand. All right, so image from next image, use path name and LucidIcon from Lucid React. Lucid React is a package that we get because we are using Shatzi and UI. What we have to do now is we have to import a bunch of packages from components UI sidebar. So that's gonna be sidebar content, footer group content and label.
And then after that, the rest of them, which is gonna be header, menu, menu button, menu item, rail and trigger. So you can pause the screen and just import all of those from components UI sidebar. Then let's go ahead and import skeleton from components UI skeleton. Let's go ahead and switch it up by importing organization switcher, user button and use clerk from clerk next JS. So we're going to use these UI components and a hook to render the organization switcher and the user button.
We already did this in the first chapter when we rendered it on a blank page but now we're going to go ahead and use it in a proper layout. Now let's import all the icons which we are going to need. From Lucid React, Home, Layout, Grid, Audio Lines, Volume 2, Settings, and Headphones. And last but not least, let's import link from next link. Let's go ahead and add some interfaces.
Menu item with a required title string, optional URL string, required icon which is a type of Lucid icon which we import from somewhere. Okay, so we import it here which means we can do it here again. So let's import Lucid Icon and I think we can prefix it with type Lucid Icon. There we go. So we reduce the amount of imports above.
And an optional onClick. Then let's go ahead and create an interface navSectionProps. An optional label, items, which are an array of menu items defined above, and a path name, which is a string. Then let's go ahead and create a function a nav section. So let's go ahead and create this function with props label items and path name and in here we're going to go ahead and return a sidebar group component.
The sidebar group component will check if the label exists because it can be undefined and if it exists it's going to render sidebar group label component with label content inside and a small class name to specify the text size, make it uppercase and give it text muted foreground. Alright, then let's go ahead and open up sidebar group content component and let's close it here And let's add sidebar menu inside. And what we're going to do here is we're going to iterate over items.map. I mean, we're going to iterate over items using .map operator. So items are simply an array of things we want in our menu.
So what we're building now is basically this. So this is like one, what is the nav section, right? We're going to have one nav section, second nav section, third nav section. It's basically like a collection of menu items. That's what we're building a component for now.
And when we iterate over these items, we're going to render them instead of a sidebar menu item component. So make sure you give each of these a key of item.title because we assume these are going to be uniques. And let's go ahead and render sidebar menu item here. And now, First of all, let's go ahead and mark it as child if we have item.url. So by adding double exclamation points you can turn any type into a boolean here.
So if it exists it's going to be true, if it doesn't it's going to be false. As child is needed because if we have item.url we're gonna have to render a link element and the proper way to use a link element inside of Next.js and the button is to use as child property So this button then becomes the link element. And let's go ahead and check if something should be active. So when should we display an item as active? For example, right now, this scenario, I am on a homepage, on a dashboard page.
And you can see that this dashboard page is highlighted so that's what is active is. So if I click on explore voices it should turn green or in our case just active right that's what is active prop is doing. So let's go ahead and create some logic for that. When should a menu item be active? Well, first let's check if we have item.url.
If we do, let's go ahead and check if item.url is a root page. And if it is, let's use a double ternary here to check if the current path name is also forward slash. So the path name is something that we have right here. All right. So if that is true, then let's go ahead and check if path name starts with item.url.
My apologies, that's not what I meant to say. If path name is not a forward slash, then let's check if it starts with item.url. Otherwise, false. The best way to understand what this does is to see it in action. Looking at it, it's quite confusing to understand what it does.
I'm sure you are thinking, why can't we just do this? You can, but very soon you will notice some edge cases. So this is kind of like an advanced way of doing that in a bit more complicated way, but the results are actually quite good. Let's go ahead and pass in item on click if it exists. This will be used for example, in voice cloning.
So voice cloning will not be a separate page. It's just gonna be a dialogue. So that's why we need an on click option. And that's why we have optional items.url for example. Alright, so that's on click here.
Tooltip is going to be item.title and then in here let's finally render something inside. So item.url check. If we have it let's use link element, let's render item.icon and span to render item.title. Each link element needs to have an href so let's go ahead and give it item.url and if that's not the case, so if we don't have item.url in that case let's render a fragment and the exact same content inside alright now I just want to see what I'm doing wrong here because I'm having some error here. So let me just check.
So this is properly closed. Return. Does it have a proper one? It does. Let me see what exactly is missing here.
I think another parent this is might be missing. So we iterate over items. Let's see where sidebar menu item ends. It ends right here. I think I'm just missing another parenthesis.
I am. All right. So that definitely helped. And I want to stop here because we wrote so much code and didn't really see what it's doing. So there is just one more class name we have to add, but I don't want to bother you with that until we actually render something.
So let's actually export function dashboard sidebar below it. In here we're going to initialize the path name and use clerk. Then let's go ahead and define main menu items and give it a type of menu item in an array. So the first one will be the dashboard with title dashboard, URL forward slash, and an icon of home. And the second one will be explore voices with URL voices and layout grid.
The third one is going to be text to speech with text to speech URL and audio lines. And the last one will be voice cloning with no option at all. Right. So I mean no URL at all. So just icon.
Great. Then let's go ahead and create other menu items. So this was main menu items. Now below it, let's create others menu items, exact same type, open an array. And the first item will be settings with an icon of settings and an on click, which simply uses the clerk hook to open organization profile.
And then the second element will be help and support. This can lead to your documentation page or it can lead to an email. So you can open a user's email client by creating a href to mail to. This is normal HTML. This isn't Next.js specific.
And in here You can write whatever email you want. So this is for example what I use for my customer support. And give it a headphones icon. So that's the other menu items. We now have two navbar sections that we can render.
So let's go ahead and start rendering things. I'm going to go ahead and return a sidebar component and I will give it a collapsible of icon. You will see what this is for in a moment. Let's go ahead and render the sidebar header. Now the sidebar header should have the following class names flex, flex-goal, gap-4 and padding-top-4 and now we're gonna go ahead and open a div element.
Now this div element will have a class name of flex items center gap to padding left of one. And now these are important. So this is all one class name group data dash collapsible icon justify centered. So let me zoom out so you can, I think I need to zoom out even more or maybe move this down here so you can see? So group data collapsible icon justify center is one class name.
And group data collapsible icon PL0 is one class name as well. Right, so make sure that you don't add any spaces when writing this. You can see it's all one class name. Alright, so now that we have that in here, let's go ahead and render an image. And this image will render logo SVG.
This should match exactly what you have added in the beginning of this chapter to your public folder. So I added logo.svg, So you should target whatever you have added in here. Let's give this an out of resonance. That's the name of our app with is 24 height is 24 class name rounded small. And then let's go ahead and render a simple span element with a text resonance.
Now we should give this a class name as well. So I want to add the most complicated one first, which is another group data collapsible equals icon hidden. So it's basically the exact same one we've been adding three times now. And then let's go ahead and add the rest. So font semi-bold, text large, tracking fighter and text foreground.
And let's go ahead and add something next to this span element, which is a sidebar trigger which can be used to collapse or expand it, but it's only going to be visible on mobile, meaning it's hidden on large devices. And give it ML auto so it's pushed all the way to the right side. All right. Now I think we can already previewed this even though we don't use any of the menu items. We have dashboard sidebar now.
So let's go back to this layout file, which we started to work with in the beginning and we can now import dashboard from features dashboard components dashboard sidebar and then let's simply render dashboard sidebar right here. I'm gonna go ahead and expand and zoom out and make sure you zoom out too. So you need to be on desktop mode to see this. And now you can see that we have our sidebar right here. So I'm gonna be zoomed out a lot simply so I can actually see the sidebar here.
All right, now let's go back inside of the dashboard sidebar and let's use the path name and our menu items. Thankfully, we already built the nav section component. So we actually have quite easy work here. So outside of this div, let me just see. So, okay, outside of sidebar header, let's add a div with a class name, border-bottom, border-dashed, and border-border.
And then let's add sidebar content here. And inside let's add nav section component, which is a self-closing component. Pass it items to be main menu items and path name to be path name. Like this. Go ahead and save this.
And now, you should see dashboard, explore voices, text to speech, voice cloning. So All of those options are now here. Now let's go ahead and add the others. So I've purposely omitted label for this nav section because I don't think it needs to have it. But for the other ones, I want to give it a label for example and let's pass in our others menu items and path name again and now you should have two sections the first one and then others with settings which should open organization workspace like this and help and support should open the email client.
All right, now that we have this, let's go ahead and go back inside of the sidebar header and let's open sidebar menu once again, sidebar menu item so we follow the proper composition and render organization switcher from clerk inside. Now in here you should see John's organization. Here are a couple of props I want to add. I want to go ahead and pass in hide personal. So we never allow the user to switch to a non-organization mode.
And I want to go ahead and give it fallback. Fallback is basically what should render here before this is loaded. Let me go ahead and this seems to have collapsed somehow. Okay, to give you an ability to uncollapse it, I'm just gonna pause this in case this has happened to you. To give this an ability to uncollapse, go down here where sidebar is almost closed and render sidebar rail.
What this should enable you to do is have this border clickable. So the right border should be clickable now. All right. In case that isn't working for you, here's another thing you can do. You can go inside of app folder, dashboard, page.
And in here you can render sidebar trigger from components UI sidebar. And you can see that now next to the dashboard text you can actually trigger the sidebar. The reason this is working just by adding a component is because in the layout file, we added a sidebar provider, meaning that each page rendered within this route group has the proper context to communicate with the sidebar provider and sidebar context. So that's why this is working. So you can now even toggle it on mobile too.
That's a cool trick and you can close it from here. So yeah, perhaps it's easier to add it here so we can develop in the mobile mode as well. Great. So make sure that you add sidebar rail still because that's what allows it to collapse on desktop mode. All right.
So we've added organization switcher now, but the problem is when you refresh you see it kind of flashes it doesn't immediately appear so in order to resolve that we can use something called fallback. And fallback here can have a component called skeleton. So skeleton, which we import from ShatCNUI, components UI skeleton. And let's just go ahead and give it a class name to match the appearance of organization switcher. So height of 8.5, full width, group data collapsible icon size eight.
This is all one class name. Rounded medium border border border I think we don't need this actually. And background color of white. So let's go ahead and see it now. There we go You can see that now there is something like filling up the space.
The problem is it still kind of jumps. Don't worry. That's because we haven't added proper class names to the organization switcher itself. And just to zoom out so you can see group data, this is all one class name. I wanna make sure you're able to write it properly.
So now let's go ahead and add appearance to the organization switcher so it looks like we expect it. If you don't want to type this out by hand you can always pause the video go inside of my source code and simply go inside of features, dashboards, components, dashboards, sidebar and you can just copy whatever I put inside of here, simply because there isn't much to learn about styling this, except knowing that you can style it. So we're first going to modify the root box. Now, the root box is going to have width full with exclamation point at the end, which makes it required. We're then going to use another group data collapsible icon and change the width to auto.
And pretty much every single class name here will end with an exclamation point. So keep that in mind. Then another group data collapsible icon to enable flex. And then another one, group data collapsible icon to enable justify center. Great.
Now after the root box, we're gonna go ahead and modify the organization switcher trigger. Let's go ahead and add the following class names here. Full width justify between background wide border and border border. We're not done yet, so it's gonna have a couple of class names. Let's go ahead and add a rounded medium, PL1, PR2, and PY of one.
Then let's go ahead and add GAP3. And let's add another group data class name, collapsible equals icon with auto. And another group data, group data collapsible icon padding one. So the group data thing is actually just making it responsive simply because on desktop you can see that we have this responsive mode And you can see it's not fully working for organization. So that's why we are adding the group data class names to recognize when it's in the collapsed mode so we can properly display it.
So let's go ahead now and add another class name here for organization preview and just make it gap2. Then let's go ahead and add organization preview avatar box with size 6 and rounded small. Then let's go ahead and add organization preview text container. This should have some text changes, text extra small, tracking tight, font medium, text foreground, and then another group data collapsible icon class name, which simply forces it to hide when in that mode. Then let's go ahead and let's add OrganizationPreviewMainIdentifier to have text 13 pixels.
And let's go ahead and do another one. Organization switcher trigger icon to have the following class name. Size four, text sidebar foreground, group data collapsible icon hidden. And now you can see it's fully compatible with being collapsed. Amazing!
And it still works as expected. Beautiful! So now that we've done those changes, let's go ahead and also do those changes to our nav item. So our nav section here renders the sidebar menu button, but we never actually give the sidebar menu button any class names. So that's what we're going to do now.
We're going to make it the same height and same padding as the organization switcher, but this will be easier because we don't have to target multiple elements, just one. So let's start with height 9, px 3, py 2, and text of 13 pixels. Let's change the tracking to tight. Let's go ahead and give it a font medium, border, and border transparent. The reason we are making it transparent is so that when it turns into active, we can simply change the color of the border.
Because If you don't do transparent, you will see a visible layout shift every time it becomes active. So this is one class name. Like this. Data. Active.
True. Border. Border. Alright. And I think that should be enough.
Let me just go ahead and see. There we go. Perfect. And if you want to add one more cosmetic change for this, I wouldn't recommend writing this by hand. If you want to, you can, but you can just copy from my source code.
Again, another data active true and just go ahead and add this specific shadow. So You can see why I'm telling you that you don't have to write this by hand. It's just a subtle effect which I like. I'm not sure if you can even notice it. If you can't, you don't have to implement it.
That's why I said you don't have to write this part by hand. You can just copy it from the source code or just don't use it. This is just UI and cosmetic anyways. Alright. And speaking about those shadows, we can actually add the same thing to an organization switcher if you want to, to make it look the same.
So in the organization switcher here, if you find organization switcher trigger, you can go ahead and paste another shadow here, which is essentially the same or very similar shadow as I've just defined above. Again, I don't expect you to do this except if you really really care about UI and cosmetics you can copy it from the source code And now it kind of has a similar shadow here. All right. Now that we have that, there is one more element that we have to add. And that is inside of the sidebar footer.
So that is the user button. So after sidebar content, let's go ahead and simply add a dashed border here. And let's open up sidebar footer. Sidebar footer should have a class name gap3 py3 and inside a normal usual composition sidebar menu, sidebar menu item. Let me go ahead and open it.
There we go. And let's render the user button inside. So this is just before we render the sidebar rail. And now at the bottom here, you can see it's interfering with this developer indicator. Here's a little trick you can do.
You can go inside of, I mean, this indicator comes from Next.js and you can modify it instead of Next.config. Dev indicators false like this. And then that should hide it. There we go. And you can now see the user button here at the bottom.
The problem is it doesn't really have the same design as all others. And now if you want to leave it like this, you absolutely can. So again, we're just doing cosmetics now. But if you don't you can go ahead and do the following. You can go ahead and enable make it look a little bit better by adding show name which kind of makes it look better.
The problem is it doesn't take the full width and height and it also flashes. If I zoom out, you can see the layout shift happening at the bottom moving the entire thing around. So I'm going to go ahead and add a fallback here. Skeleton, class name, height 8.5, full width, group data collapsible icon size 8, so this is a single class name, rounded medium, border, border is not needed actually, actually it is, it is. So make sure you add border, border and background color of white.
So that's going to make it jump less. And now we have to also change the appearance of the user button. If you want to, you can just go inside of the source code, it's free, and you can copy the appearance in the user button, just find features, dashboards, components, dashboards, sidebar. So we're going to go ahead and add the elements inside of here. And let's go ahead and add class names for the root box.
The root box will have with full group data collapsible icon with auto and then we're gonna have group data collapsible icon flex group data collapsible icon justify centered so not enough to actually see any changes because we have to now add some class names to userButtonTrigger. So in here, I'm just going to go ahead and add widthFull, justify between. Then I'm going to add background white border border border around the medium and then we're going to add some padding here. So that's going to be PL1 PR2 and PY1. There we go.
You're starting to see the border here. And now I'm just going to go ahead and add a shadow. Again, you can copy and paste this. I don't expect you to write it yourself. Alright, we have that.
Now let's go ahead and see what else we have to do here. Let's go ahead and add group data collapsible icon with auto. Let's add group data collapsible icon padding of 1 and let's add group data collapsible icon after hidden then let's go ahead and add another element here user button flex row reverse and gap to Then let's go ahead and change the user button outer identifier. So this is another text size force. And let's go ahead and add some tracking, tracking tight, font medium, text foreground.
Let's go ahead and add PL of zero and group data collapsible icon of hidden. And let's go ahead and change user button avatar box to be size 6. As I said you can just copy a bunch of these from from my source code. Another one I forgot. In the user button trigger, you have to add another class name, which is this one.
At this point, I think you've definitely noticed it's just easier to copy from the source code simply because it's a bunch of confusing class names. It's basically just cosmetics to make this look a little bit nicer and you can see all of them kind of match now and you can see there's there's minimum shift going on here. Great. So all of this seems to be working fine now. If you attempt to go to any of these you will get 404.
We have a nice working organization. We can open the organization settings from here or we can click on settings here. We can go ahead and manage our account from here and we can even sign out and that should redirect us to this page. One thing that I'm noticing it's not working as expected here is when I log out. Yes, when I log out, I'm not redirected to my login screens.
So that's something I forgot to do. And it's a very, very simple fix. So you have to add additional class names to your .environment for the next clerk pages. So it's these four. Next public clerk sign-in URL, sign-up URL, after sign-in URL, and after sign-up URL.
This way you make clerk aware that you're using custom sign in pages, which should match exactly these routes. You don't have to do it. But if you since we developed custom ones, it makes sense that it redirects us here, right? And now after I log in, there we go. John's organization, beautiful.
So that is part one done. Now let's go ahead and build the last part which is essentially the home page with input and actions. So in order to build the actual dashboard page we need to add wavy background. So this is a cool effect from S Eternity UI if I pronounced it correctly and it's basically this cool background effect. Of course, if this is not something that interests you, you can skip this.
So we can add this using ShadCN simply because ShadCN is a registry. So I'm going to go ahead and run this command in case you cannot run this for whatever reason if it's changed if it looks different. Always remember you have access to the source code and you can just go ahead and find the component you need. So I'm gonna go ahead and run npx chat-cn and I'm just gonna use the same version that I've been using, which is 3.8.5, I believe. Let me go ahead and confirm.
It is 3.8.5, All right. Simply so, you know, it's compatible with all of the other components I've added. So let's add asEternityAvayV background and now you will see what this will do. So it's going to install a dependency and it's going to create one file. So let's go ahead and look at the packages which were added.
So the only package which was added is simplex noise. So if for whatever reason you can't use this, you can do manual npm install simplex-noise like this. And the component which was added is inside of source components UI and it's probably all the way to the bottom because of the letter alphabetical, wavy background. You can see it only depends on the simplex noise package. It has a bunch of errors so I would recommend that you immediately go inside of wavy background here and just go ahead and do a quick fix and disable the following one.
So disable this for the entire file. So slint disable typescript slint no explicit any. And then go down here and find another one expected var use letter const you can just do quick fix and disable no var for the entire file Simply because the component works as is It's just cosmetics, we don't need to worry about this too much. By disabling this lint rules, it will not prevent your app from failing to build. So that's why we are doing that, to make sure no components are preventing the build from happening.
Great, so now that we have wavy background, if you are interested in using wavy background, you need to do one change here. So alongside wave opacity, let's also introduce Wave Y Offset. Wave Y Offset is going to be a new custom prop we create. It's going to be optional. It's going to be a number.
And let's save the file. And let's go ahead and find where we... Let's go ahead and do context line 2. Alright. So search for context.line2 And go ahead and delete this comment and we're just going to do a slight modification.
So it's going to go ahead and use not 0.5. It's just going to be plus wave y offset. And it's not gonna include the height in the calculation. So it's just gonna be x, y and plus wave y offset. Again you can just copy this from the source code.
Okay. So we just did this slight modification. What I want to do now is I want to create a component called Hero Pattern. So I'm going to go inside of features, dashboard components, hero-pattern.tsx. I'm going to import that wavy background which we've just modified and added.
I'm going to export function hero pattern. I'm not using a default export here because this is not a reserved file name like page or layout which require a default export. So I'm using a named export in all of my components if you haven't noticed. And in here we're just going to go ahead and do a very simple div with the following class names pointer events none so this isn't clickable absolute inset zero hidden overflow hidden and only visible on desktop devices. And now we can render the actual wavy background and let's just go ahead and give it the following props.
I'm just adding some colors here. You can of course modify this some background fill blur of 3 speed of slow then we're going to add some wave opacity and wave width and then we're going to use our new prop wave y offset. Let's go ahead and add a container class name of height full and let's give it a class name of hidden by default. All right So now that we have the hero pattern, I want to go ahead and render it so you can see your work. So we're going to go ahead inside of Features, Dashboard, and we're going to create a new folder called Views.
And inside of here, let's go ahead and create dashboard-view.tsx. I'm going to import hero pattern from components hero pattern and I'm going to export function dashboard view, return div with a class name of relative and render hero pattern like this. And you can import this either like this or you can always explicitly import from dashboard components, whatever you prefer. Alright, and now that we have the dashboard view, let's go ahead inside of app folder, dashboard, page.dsx and let's go ahead and change the return to be dashboard view. This way we develop the UI inside of the features folder and not inside of the app router.
The app router is used for routing, prefetching and server components. You can remove the sidebar trigger import as well. And now when you expand on desktop mode, Looks like nothing is appearing. I think it's because we're missing some other components. So for now I think we did everything correctly.
Let me maybe try commenting some things out here. Is it because of the class name hidden trying to make it appear. All right. I still can't see it. Never mind.
We're going to see it later. So leave it unchanged. And now let's go ahead and create a page header component which we're gonna use to bring back the sidebar trigger on desktop mode. So we're gonna go ahead inside of source components create a new file page-header.tsx so I'm purposely not developing this instead of the UI folder. I want to reserve the UI folder for chat.cn added files so I know that all files inside are not something I've wrote but all other ones are.
So in here we're going to import headphones and thumbs up from react, lucid react, link from next link, we're going to import button from components.ua, button, sidebar, trigger and cn from libutils. Let's go ahead and export function page header with title and class name for props class name is optional and in here let's go ahead and return. Now I want to start with a div which will use the cn function. This cn function allows us to write dynamic class names in Tailwind. So CN accepts an infinite amount of parameters.
So you can see it really does not have an end to how many parameters you can write. The point is this works better than doing something like this, flex blah blah blah, and then class name. This is because if you pass undefined, it would literally render undefined in your list of class names. CNUtil takes care of that and many other things such as overriding class names, proper ternaries and things like that. So it's always recommended that you use this.
And it's honestly better developer experience too, you just pass your dynamic class here. All right. Then let's go ahead and open a div with flex items center and gap two. Let's go ahead and render sidebar trigger. And let's go ahead and render a heading with text large, font semi-bold and tracking tight and render the title inside.
And then what we're going to do is just render two buttons. So flex item center and gap 3. So this will be rendered on the left side and this will be rendered on the right side because we are using justify between. So within this container the first element is on the left side and the other element is all the way on the other side because we justify between those two elements. So let's go ahead and add a button variant outline size small and as child property and in here let's just go ahead and add hrefs to our support emails.
Let's render Tom's up and this will be if anyone wants to send some feedback, but hide this on mobile. It should only be visible on desktop. And then we're going to do the exact same thing. Another button, another link to our support and just need help text size outline as child. Everything is the same.
Great. So now that we have the page header, we can actually render the page header too. So let's go ahead inside of our dashboard view, features, views, dashboard view, and let's go ahead and render page header. This is a reusable component that we're going to use in text to speech, voices, everywhere. That's why I didn't create it in the dashboard features but rather in our common components.
Page header should have a title of Home and a class name of LGHidden. So you can see on the mobile you should have Home and you should have an ability to open this. But on desktop it's going to be hidden because Dashboard is a very specific page which will have its own header. Alright? And these two buttons should just open an email and nothing more.
If you want to you can use dashboard instead of home so it matches what you see in the sidebar. Alright. Now that we have that, let's go ahead and go below the hero pattern and add a div class name relative space y8 padding of 4, lg padding of 16 And in here let's develop the dashboard header. So dashboard header. Let's go ahead and develop that now.
Let me just go ahead and find this component here. Okay. So, dashboard header is currently not defined, so obviously it's throwing an error. So let's go inside of features, components, dashboard header.tsx. Let's mark it as use client, import use user, headphones, thumbs up and link from next link.
Let's also import button from components UI button. Let's go ahead and export function dashboard header and let's use use user hook from clerk. And from here we can extract is loaded and user. Now we're going to greet the user with their name thanks to this information from Clerk. So let's do some simple composition here.
Flex items start, justify between. Then let's go ahead and open a div, space y1. Let's render a paragraph, nice to see you, with text small and text muted foreground. And then in here let's go ahead and render a heading element. A heading element will have a class name of text2.xl on large text3.xl font semibold and tracking tight.
:02 It will check if the clerk has loaded. If it has loaded, go ahead and open parenthesis and check if user object has full name. If it doesn't, fall back to user first name. And if it doesn't, fall back to there, meaning we weren't able to load the user's name and if it didn't load just use three dots like this And now let's go back instead of the dashboard view and let's import dashboard header. Again if you want you can use dot dot or you can directly import from here.
:44 And you can see how while it's loading it's three dots and then it says nice to see you John Doe or whatever your account name is. Brilliant. Now that we have that let's go outside of this div which is encapsulating the header and we're just going to go ahead and repeat those buttons. So I think it makes sense for us to go back inside of page header and let's just copy them. As I said, this is a very specific case for dashboard page.
:19 Usually we will not do this much code duplication. So go back inside of components, page header and copy the div which holds these buttons. The feedback and the need help button. So just copy those to go back instead of the dashboard header. And just add those buttons in here.
:43 Let me just fix the indentation here because I feel like something wrong. All right. And now in here, let's see what we should do. So the first thing we should do is hide these on mobile like this and change the flex to only appear on large devices, like this. So now you can see that mobile is using the shared page header, but desktop has its custom buttons here.
:23 And you can see the wavy background starting to appear. So yes, I was right. We need to add more elements before we can see it. So let's just keep developing. I wanted to show you the wavy background earlier, but I ended up just, you know, confusing you with all the changes.
:43 Sorry about that, but we will see it very soon. All right, so now we have nice look both on desktop and on mobile. Looks very very nice. What we have to do now is we have to develop the text input panel. Before we can do that, let's go ahead and prepare another feature folder.
:08 So instead of features, we're going to have another feature called text to speech. We are not developing it yet, but we are kind of preparing for it. You can see that this element here, start typing or paste text is the text to speech functionality but we are kind of borrowing from this now. So I'm going to go ahead and go inside of text to speech and I will simply create a new folder called data and inside constants.ts and I will export text maximum length and make it 5000. So I technically didn't have to do this already, but since I know that belongs to that feature, I'm going to develop it here.
:56 Great. So now that we have this, Let's go ahead and let's develop the text input panel. So we're going to go inside of components and let's do text input panel.tsx. Let's go ahead and mark this as use client. Let's import use state, use router from Next Navigation, coins from Lucid React, badge from Components UI badge, button and text area.
:27 And then we can also go ahead and import our new text max length from features text to speech data constants. You can also let it autocomplete if you're ever, let me see if it will work. Text max length, yes you can see Autocomplete works well. So now let's go ahead and export function text input panel. And for now this won't be a real form.
:00 So usually when we develop forms we're gonna use 10 stack form but since this isn't an actual submit form, it's just decoration. This is just the homepage which will redirect the user to text-to-speech functionality once they type in something here. So this is mostly just a marketing page right here. That's why we will use a simple useState. And that's why we're also going to need useRouter to redirect the user later.
:30 So let's go ahead and prepare a very simple handle generate function. So when user clicks generate in this text input, what's going to do is it's going to take the text the user has written and it will redirect the user to text to speech with that prompt in the URL so we kind of pre-fill it with initial value so the handle generate button will do exactly that it will trim the text so we can check if it's empty and if it is we will return otherwise we're gonna do router.push open backticks and we're going to redirect to text to speech and we will append a text param and we are gonna make sure to encode URI components so nothing unsafe is passed like this. There we go. Perfect. Now let's go ahead and let's return a div and let's go ahead and start adding some class names to this div.
:41 Let me go ahead and expand like this. We're going to start with rounded 22 pixels followed by BG linear 185. Then we're going to start the gradient from a specific hex color. We're going to add from 15% via another hex color, so this is one class name, don't mind the collapse. Via will be 39% and then we're gonna add to another hex color to will be 85% and let's go ahead and add a very subtle padding of 0.5 and a shadow with 0, 0, 0, 0, 4 pixels white.
:31 Like this. Then inside of here we're gonna go ahead and render a div with class name rounded 20 pixels and a very specific background. You might be wondering why am I getting this class name right here? Well, and also how do I see the content of my Tailwind classes? How do I have these colors?
:57 Well, all of that is thanks to extensions. So, first of all, make sure you have Tailwind extension on. Tailwind CSS IntelliSense. This will allow you to see the content behind the class names. And you can deduct if you have an invalid written class name, because when you hover, nothing appears.
:17 So every time you hover and see the content inside, you know that you have a properly written Tailwind class name. And you also get warnings like this. So rounded 20 pixels can be written as rounded for Excel. This is the one specific case where we don't want to do this. And I'm going to add a little comment why.
:39 So let me go ahead and open a comment here. So just so you understand why. Using pixel values for border radius to ensure proper gradient border math. So I will try to demonstrate later what would happen if we used rounded for Excel, which is percentage based, rather than 20 pixels. So I'm going to add a follow-up comment here.
:07 Standard classes like RoundedForExcel use, my apologies, not percentages, they use calc, which doesn't align cleanly at corners. You will very clearly see the difference. So that's why I'm forcing pixels here and not using 4XL. Alright, so that's me sharing some border knowledge with you here. Now inside of here let's open another div with a class name space y4 rounded to excel background color of white padding of 4 drop shadow extra small.
:47 And now inside of this div we have the input area. So let's go ahead and render the text area. Well, I should have said text area. Well, we don't need a comment for something that's obvious. Right?
:02 Let's go ahead and give this a placeholder. Start typing or paste your text here and let's give it a class name. Minimum height of 35, resize none, border zero, background transparent, padding 0, shadow none, focus visible, ring 0. Let's go ahead and follow it up with some attributes value text, onChange, get the event and call setText with event target value and maximum length is textMaxLength which we've defined previously. So I want to render this already so we don't build anymore without seeing what we are actually building.
:52 So the place to render this is in the dashboard view. So let's go inside of dashboard views, dashboard view and right below the dashboard header let's add text input panel. And again you can choose how you want to import these. I prefer explicitness. And you can see the subtle effect.
:21 So it's kind of gradient. You can see it even has this. It's a really, really cool design. And I think I can now demonstrate instead of text input panel. If we go here to rounded 20 pixels.
:36 If I change this to rounded for Excel, you can see what happens. You see how the border looks odd? So when you use pixel based, it will properly be calculated. But when it uses the tailwind for Excel it uses calc and then it messes up on the borders. So that's what I was trying to show you.
:03 All right. So now that we have the actual text area, we should go ahead and develop the bottom info. So the bottom info will be a div with class name flex item center justify between a badge with variant outline class name gap 1.5 border dashed a coins icon rendered inside with size 3 and text chart 5 color. Then let's go ahead and render span with text extra small. And let's check if we have...
:46 Let me just go ahead and create a proper ternary here so if text length is empty we are simply going to render start typing to estimate but if it is not empty we're going to return a fragment and in here we're going to go ahead and calculate using and calculate what the estimated cost of user's prompt will be. So we are going to multiply the text length, basically each character, with 0003. That's because that is our price, which currently is just a magic number, but later when we add billing with polar, it will make sense because we will have usage-based pricing. So for every character, we're going to calculate how much this will cost. And make sure to add the dollar sign at the beginning here.
:43 So you can see now, it says start typing to estimate. And the more I type, the bigger the number gets. So it's using characters to calculate how much this will cost you. Let's go ahead and add an empty white space estimated. So they know this is an estimation.
:06 We cannot guarantee it won't be calculated in a different way. Now let's go outside of this badge and let's go ahead and render how many characters we have left. So text extra small, text muted foreground, and we're going to use text length to locale string, and we're going to use text max length to locale string characters. There we go. So let's go ahead and see that.
:37 There we go, zero out of 5, 000 characters. And I think this should look good on mobile mode too. So let me switch to iPhone SE. I use iPhone SE as like the smallest possible device that I want to support and it looks good here. So I wanted to show you this because when I collapse it myself, it looks broken, right?
:02 But this is smaller, I mean, thinner than the smallest device I want to support, so I don't really care about this example, I care about this, right? Alright. And now let's go ahead and render the action bar. So let's go ahead and go after we close this span, after we close this div and then another div let's render the action bar. The action bar will be a div flex items center justify and and padding three and it's simply going to render a button.
:36 Size small, disabled if there is no text which is trimmed so you aren't allowed to enter whitespace. On click handle generate which will redirect the text to speech with the text. Class name with full, LG with auto. And generate speech. So you can see now it's disabled until I start typing and it's full width on desktop on mobile but on desktop it transforms to a corner button.
:07 Beautiful, beautiful work. All that's left is to create quick actions, which are basically just redirects with some predefined prompts. So let's prepare with QuickActionsData. QuickActionsData can be whatever you want. I'm going to show you an example.
:28 So let's go inside of Source features dashboard. Let's open a new folder called data and inside let's go ahead and create quick-actions.ts I'm gonna go ahead and export an interface quick action which is a title, description gradient and an href. And then I'm simply going to export an array of quick actions. So for example, the first one can be narrate a story with a description, which can be something like bring characters to life with expressive AI narration. The gradient can then be something, it needs to be proper tailwind.
:14 So from CN 400 to CN 50. And the href would then be a text to speech redirect. And then in here, the content you want to auto fill. So this can be something like this. In a village, tucked between mist covered blah blah blah.
:37 So basically a prompt. I suggest you just go inside of the source code and find features, data, quick actions and just copy this thing. I mean this is just marketing gimmick. This is just some prompt suggestions that I generated with AI. You can generate yours with AI too.
:59 You can just select this and tell it to generate five examples like this. So that's exactly what I'm going to do. It makes no sense for you to write this by hand. It's important that you have a title, description, gradient, and href and have six quick actions. So I'm just going to add the rest.
:20 So I have guide a meditation, introduce your podcast, voice a game character, direct a movie scene, record an ad, and here is my missing semicolon. And I believe that's six. One, two, three, four, five, six. That's right. So just make sure you have six quick actions.
:41 Here, you can just add some placeholders if you have no idea, or just use AI to generate a couple of examples. It's important that each href leads to forward slash text to speech. And since we're going to be using Next.js link, it will automatically encode this so you don't have to worry about white space. I'm not sure if this is like the best way to do it, but it works. So we can leave it like this for now.
:08 It's a simple marketing landing page, right? All right, so once we have Quick Actions, we have to go ahead and actually develop these. So let's go ahead inside of components and let's develop QuickActionsPanel.tsx and for now all we can do is we can import those newly created quick actions. You can again use this or you can use features, dashboard, data, quick actions. Let's go ahead and export function quick actions panel and Let's go ahead and return a div space y4 and inside let's add a heading quick actions with text large and font semi bold and then let's create a grid with gap 4.
:04 Let's go ahead and use 2 columns on medium devices and on extra large devices let's use 3 columns. And then let's go ahead and iterate over our quick actions. And each action, Let me just add another parenthesis here. Each action should be rendered within a quick action card. So we don't have this yet.
:26 We're going to develop it in a moment. Make sure you pass in the key title description gradient and an href Like this. So that's the quick actions panel. And now let's go ahead and develop the last component in this chapter, which is the quick action card. So the quick action card will have link from next link, arrow right from Lucid React and let's go ahead and import type quick action from data quick actions or if you want to be specific you can always use this and you can use type on the inside or on the outside if all of them are going to be types which is the case for this one.
:11 So quick action is the type we define here the interface more specifically. All right. Now let's go ahead and also remap the quick action type to be quick action card props. And then we can just export function quick action card map the props and have title description gradient and href because all of these are exact same props. So quick actions panel is passing the exact same props as we have inside of our quick action and quick action here.
:45 That's why we can share all of these components and their props. Inside of here, let's go ahead and start with a div. Flex Gap 4, rounded extra large, border, background card and padding of 3. And I want to stop right here and go back inside of Quick Actions panel and import Quick Action Card from ./.quickactioncard or if you prefer explicitness you can import from here. And then let's go ahead inside of dashboard views dashboard view and let me just check the exact place to render this.
:31 Alright, So after text input panel quick actions panel. And I like to be specific so I'm just going to go ahead and replace this import. So you should now see quick actions text and you should see six empty boxes perhaps on desktop. It's a bit more visible. So they are very, very light.
:52 And now we will actually see the development of them. So let's go back inside of the quick action card. And let's let me just go ahead and find the component I'm developing. So we're going to go ahead and create a visual placeholder with gradient now. This is just to make it look pretty.
:25 Class name, we'll use CN from Libutils, so just make sure you add an import for that. So now let's add the static ones. So that's going to be a relative height 31 with 41 shrink 0 overflow hidden rounded extra large and background linear to this is bottom right. And then let's add a comma and let's add gradient here. Gradient is our valid class name which we define in the quick actions here.
:06 So that's why I told you these need to be valid tailwind classes so we can render them properly. All right. So Let's continue working in the quick action card. All right. And now let's go ahead and add some decorative elements here.
:26 So this is just going to be some fun design now. Absolute, inset zero, flex, items, center, justify, center. And let's add div, size 12, rounded full background white with 30% opacity. So this is one line background white with 30% opacity and then let's go ahead outside of this div and just render another one to make it have another border absolute inset 2 rounded large ring 2 ring inset ring white with 20% opacity and then we're gonna go ahead outside of this div and we're going to render the content. And the content will have a div flex flex skull justify between and py of 1.
:20 It's going to have another div which is a space y one. It will have a heading three element text small font medium rendering the title and beneath it there's going to be a paragraph with text extra small text muted foreground and leading relaxed rendering the actual description and then to wrap it all up outside of this div we're going to add a button it's going to have the following attributes variant of outline size of extra small class name of width fit and as child The as child is required because we're using link with an href prop and try now arrow right icon. So I began explaining as child we already had this scenario is required so this button then becomes the href element otherwise it will be semantically incorrect. Alright so let me just see if this looks... Okay, so this is a specific zoom level where it doesn't look perfect, but this is a 100% zoom, so it looks normal here.
:28 But yeah, on 150, before it collapses into a two-column grid, it looks a little bit weird. So you can go ahead and do the following if you want to. You can go inside of quick actions panel and you can go ahead and decide when it should break. For example I can use LG for grid calls 3 and everything else to be grid calls 2 or do I need specific MD? Okay, so we need on medium grid calls 2, on large grid calls 3 and otherwise grid calls 1.
:04 I mean, this is by default, so I don't think you even need it. Maybe not the perfect solution, but yeah, you can play around with it until it matches the design you want. I think this mostly looks good. There's like one small edge case, one specific zoom. So what should this do when you click on it?
:29 When you click on it, it should redirect to text to speech which is a 404 but if you take a look at the URL you should see the exact text and you can see it's properly encoded. And the same thing should happen here so hi there my name is Antonio And if I go ahead and click generate speech, I should see the exact same redirect to text to speech with encoded text. Hi there, my name is Antonio. Beautiful. So we have completely developed everything we envisioned and we are now ready to create a pull request so you can actually have proper CI CD in this.
:11 So we added 20 changes, some of them by hand Most of them by hand, but some of them outside of our manual changes like components.json which added the acidernity registry because we added the way we background using the chat.cn component. If you didn't, you won't have this change. So no worries about this really. We did next config to hide the developer indicators, we installed some packages which automatically modifies the log file, we added the logo, we deleted some test files And then we just created all of these other ones. Beautiful, beautiful work.
:49 So this is what I want to do next. I want to go ahead and check if we can build our project because I don't want to create any pull requests before that is possible. That seems to work. And let's do npm run lint, which should work as well. So warnings are fine, errors are not.
:09 So you can see we only have a warning in the way we background because we properly fixed the SLint problems. Beautiful! So now, let me go ahead and do git checkout-b and in here, I'm gonna go ahead and call this 0-2-dashboard So that's my branch name. And then I'm going to do git add to stage all of these 20 changes. You can also see how the branch has now changed here too.
:46 There we go. So now we staged those 20 changes and then we're going to do git commit m02-dashboard. And now those 20 changes have been committed. And then I'm going to do git push uorigin and let me go ahead and do 0 to dashboard. What this is going to do is it's going to push the branch to remote.
:12 Now let's go ahead and visit our GitHub repository. So when you visit your repository here, you should see 02-dashboard had recent pushes. And in case you can't see a button to create a pull request, you can always manually go inside of pull requests and click new pull request. The base should be your main and then you should select 0 to dashboard and then you can see all of the changes we just did. So let's click create a pull request right here and let's just click create pull request and then we're going to review our changes.
:51 As you can see, I'm using AI assistance to review my pull request and in this specific example it actually found some really important issues with the changes we've done. For example, I cannot pass up to 5, 000 characters without exceeding browser server limits. I also have an invalid query here. You can see the letter text is missing. I completely missed that, but CodeRabbit fixed the malformed query param.
:21 So what is CodeRabbit? CodeRabbit is, in short, an AI review tool. It is much more than that. The thing is, it understands the context of your project unlike any other tool you can find. So it's much more than just a AI prompt which says, analyze the pull request.
:40 It will, you will see how we are gonna create more and more branches and pull requests. It will have a deeper and deeper understanding of the project. And using the link on the screen, you can actually go ahead and review everything it can do. You can try it for free with a two-click install and add it to your GitHub and every subsequent pull request will be reviewed by CodeRabbit. Not only can it generate reviews like this, for example, a list of new features, we have added some chores which we've done and basically a summary for this pull request, It will also find serious issues in the project.
:19 So all of these are minor right now. For example, this one is a major. This is a completely broken quick action link. But later on when we develop some business logic, this is where it will come even more in handy. It has saved me countless time in previous projects where I introduced some accidental serious security issues and CodeRabbit caught them.
:40 So it's always a good idea to have another set of eyes take a look at your pull request even if you're developing solo. Alright, so yes, this is something we will fix in another chapter. I'm not sure if you made the same mistake as me, but yeah, basically we have to lower the amount of characters we can push via the query string and I also have to fix the narrate a story one because it's missing the text parame. You can see this one has question mark text, but this one doesn't. So it's an invalid card which CodeRabbit has noticed.
:13 Amazing job. So now let's go ahead and merge this pull request. And once we merge this pull request, it will count as a commit to main, which means that we now have Railway building our app again. So let's wait a second for that to rebuild. And one thing I forgot to enable but basically Railway has an ability that every time you open a pull request, it can create a temporary preview of the pull request.
:51 So if you don't want to merge it to see it deployed, but want to see a preview URL, you can do that with the Railway. So once you are in your project here, click on settings above and click on environments. In here you have PR environments. By default this is disabled, so you can just click enable PR environments and all future PR environments will be deployed. In here it asks for the base environment.
:20 It basically means that variables from this environment will be used in the PR environment. So I'm gonna select production because we have some important environment keys there. Now obviously you would go even deeper into separating the production, preview and local, but for ease of use and for simplicity sake, I will set up the settings like this and we're going to see it in action in a future pull request. For now, let's just confirm that Railway is able to build our current most recent change. And there we go.
:55 Deployment has been successful. And now, if you go ahead and visit the Production Railway app, you can see our most recent changes available right here. Beautiful! Everything is here. Let's check out desktop.
:10 Works beautifully. Amazing, amazing job. So that is our CI-CD process from now on. Every time we complete a feature, we're going to go ahead and open a pull request. The pull request will automatically create a preview URL from railway, a review from CodeRabbit, and then we're going to merge that and safely keep track of all of our changes.
:34 Amazing, amazing job and see you in the next chapter.