In this chapter, our goal is to create a studio layout. We're going to start by creating a studio route group, then the actual layout, and finally, we're going to protect the studio routes. This will be quite similar to our initial basic layout, if you remember it from chapter two. So yeah, this is what we're going to implement. And luckily for us, we can copy a lot of things from there.
So just to confirm, this is the layout we are going to be building. Right, so just this part right here. And the other part is going to be the page. We'll come to this later when we actually have some content that we can render now. But for now, we're just going to use this.
As you can see, we will have this special component right here, which is something different from the basic layout. And this one will be maybe interesting because we are going to have to implement user's procedure, which is something that we did not have up until now. And it will also be an example of us using TRPC use query without any prefetching or suspense. And I will explain why. Well, I can already tell you the prefetching doesn't really work inside of layout files.
So it's best to use it inside of page files and just not do it inside of layout files. And it will also be our first proper protected procedure. You're gonna see all about that. We can even try and prefetch so we can see if we get an error. All right, let's go ahead and do that.
Let's start by creating the Studio Route Group. So we're going to go ahead inside of the Source App folder and we're going to create inside of parentheses studio route group. Inside of this route group we're also going to have an actual route named studio and now I'm just going to add a page.tsx inside and I will simply return studio back. Since we added this route inside of its own route group it will not have this layout. So if you go to localhost 3000 studio, you will just get a text which says studio without any of the layout that we have because it's not a part of the home route group.
Before we go on, let's go back to our localhost 3000 and how about we add a nice studio button here in this user drop-down. This is something that I briefly mentioned I believe but didn't really develop completely. So let's go inside of source, modules, auth, components, auth button And there we go. We have add menu items for studio and user profile. What we can do here from a clerk next JS, actually, I don't think we have to import anything.
I think we can just open up the user button instead of having it be a self-closing component. And inside we can do userButton.menuItems. And then we can do userButton.link. And we can simply... This is actually a self-closing tag like this and we can give the user button dot link a label of my profile an href of my apologies not my profile this will exist but later an href of slash studio and a label icon of clapperboard icon from lucid react.
And let's give the clapperboard icon a class name of size four like this. So this is supposed to be menu items like this. So now if I click here, there we go. You can see that I have a Studio Redirect. In case you forget to put UseClient at the top here, I think that this will lead to errors where it says that userButton.link is an invalid export.
So in case you got that, it's because of this. You need to add useClient to the top and then you will be able to use links here. If for whatever reason you are struggling to do this, well don't worry, you know, you can simply add a button outside of here, add a clapperboard icon like this, studio, and just add, you know, a link from next link to lead to studio. So I will just give you an example, just in case you're not able to add any menu items here. You know, there we go.
It's a button and give it an as child prop. There we go. And if you click here, you can now go to the studio and it will only appear to logged in users. So whichever one you prefer, you can also give it a variant of secondary to make it look a little bit better. There we go.
So a nice studio button here. If you prefer that, you can do that. Otherwise, you can just put it inside of the user button menu, menu items. And also you can rearrange these things. So if you want to put manage account to the bottom, you can do that, but then you have to explicitly say that.
So you have to add userButton.action and target it by label which is manage account. Make sure you don't add a typo here and then this will recognize that this comes from here and there we go. Now manage account is below the studio just in case you want to change that here. I will remove this to do here, and I will add one here to do, add user profile menu button. There we go.
So now we can use our clerk user button to go to the studio. And now let's go ahead and let's develop the actual UI. So let's prepare by going inside of our modules and let's create our studio module here. Let's go ahead and create UI. And let's go ahead and create layouts and what we can do is we can copy we can actually copy the entire home layout here so how about we add it here home layout and what I'm going to do is I'm going to rename it to studio layout like this We can ignore these two errors for now.
Let's just go ahead and rename all instances of HomeLayout to StudioLayout here. Like this. There we go. So now let's go ahead and let's copy... Well, I think we can copy all components from UI here and just put it here instead of UI.
And I think that now if I reload this window everything should be okay. If I go inside of components I'm not sure do they depend on something here. No, looks like they're pretty self-isolated. No errors in each of the components. Great!
So we just easily copied the entire layout and then we're just modified to work for our studio case. So let's go ahead inside of our app folder, Studio, and we're going to create layout.tsx for the route group, not for the route, but for the route group, right? And we can copy the content from here because they're gonna be pretty similar. Instead of importing the home layout, we're going to import from modules studio, studio layout, which in turn will be studio layout. Like this.
So now, if you refresh, you can see that I am on studio and everything is exactly the same. Except it's not supposed to be the same. So let's go ahead and start by how about we first modify the navbar. I think that's a bit simpler to modify. So let's go inside of the studio layout and let's go ahead and let's rename the home navbar here to be studio navbar.
And if you're being asked to update the imports you can press yes and that will update this import to Studio Navbar. So for now, we just change the name of the folder. So let's go inside of this new Studio Navbar instead of index.tsx and let's replace this to be Studio Navbar as well. Now we have an error inside of our layouts, StudioLayout. We have to replace the name of Home Navbar to Studio Navbar.
There we go. So now this is a Studio Navbar, but nothing's really changed. Let's go inside of this Studio Navbar here and let's modify it. So this image logo can stay, but this will change to studio. So now when we are on studio, this here says studio, like that.
And you can decide what you want to happen once you click on that. I think it's actually better if it stays on Studio. So when we are in the home, right, if I go to cars and vehicles and click here, I want it to reset to its homepage. But if I go to Studio and if I click on Studio, I wanna stay on the Studio because we will have some sub routes in the studio as well. So I think it makes sense to change this as well.
And we are actually not gonna have to have any, we're not gonna have to have any search bars so we can remove that, but we do now have a little issue and that is that we need spacer here so how about we just add a div with a class name Lex1 simply for this to be pushed at the end there are of course many ways you can do this but this is one of them. Okay so we got rid of that part which we don't need and now let's go ahead and focus on this. You can see that the studio has a border and it seems to have some shadow here. The same will be true for the sidebar. So that decision came from the original YouTube and YouTube Studio.
They have, for some reason, different layouts. So I will follow that as well. And I think it's nice because it's pretty clear that you're in a different application or at least you know you're supposed to feel like being in a different application so I agree with that. While we are here let's remove the search input import and we can remove it from here. Make sure you don't accidentally remove the one from your home, right?
You need it here, right? Make sure you don't accidentally delete this one. We are working inside of a studio. All right, so yeah, technically this no longer has to be a folder, but let's leave it like this for now. And let's go ahead and modify this a bit by adding at the end of this studio knob bar border bottom and shadow medium.
Like this. There we go. We can already see the difference clear as day. Now let's go ahead and let's close the Studio knob bar for a moment and let's replace, I mean rename our Home sidebar to be Studio sidebar and click yes to update the imports. And you can go back inside of your studio layout and you will see that now it has changed this.
But this is still called home sidebar. So how about we change that by going instead of studio sidebar here and renaming this to studio sidebar. And now we have an error here because we have to replace both instances of Home Sidebar with Studio Sidebar. Nothing much to change yet, but you should get, well, a different... Nothing, nothing should change.
Sorry. All right, let's go inside of the studio sidebar here. And how about we start by giving some specific, I want to basically give it this borders here. I'm just not sure what is the correct place to do that. I think we can just remove border none here.
And there we go. Now it has a nice border as you can see right here. Great. And now we're going to have to modify the content here. And the content will actually be much, much simpler than in my home sidebar.
So I'm thinking, do we even need this kind of separation here? I think it's easier if we just, you know... Yeah, but okay, let's go ahead and still call this main section, but let's go ahead and... All right, let's see, how can we do that? Instead of having personal section, we're just going to do this.
We're going to add a sidebar menu item from components UI sidebar, so make sure you add this input then we're gonna have sidebar menu button from components UI sidebar this will be a tooltip exit studio and it will have an as child prop then we're going to add a link from next link here and the href will lead back to the root page and inside of here we're going to add a logout icon from lucid react so make sure you have added this Let's give the logout icon a class name of size 5 and let's give this a span of exit studio and a class name of text small like this. There we go. So now we have exit studio but something seems off here. I might have forgotten something. I think that I need to wrap my sidebar menu item inside.
Yes, it needs to be inside of sidebar menu in the first place, I think. Yes, yes, yes. Let's do it like this. Let's make sure to import the sidebar menu. There we go.
And let's also add sidebar menu group. Is it called group or is it just sidebar group? It is. And I think that sidebar group should actually encapsulate the sidebar menu. There we go.
So I think that now everything should be aligned nicely. There we go. So now we have the exit studio here. And what we're going to do is very simply, we're going to copy, we're going to copy, let's see. I'm just trying to think of the best way to do this.
All right, we're just going to copy the sidebar menu item here. So make sure you have two of those. And this one, very simply, is just going to lead to slash studio slash videos like this. And this will be an icon, video icon from Lucid React. And this will simply say content.
Actually, it can go to slash studio, right? This, basically this page right here. And we can use path name here. Use path name. And we can mark this one as active.
So let's see, it should be the sidebar menu button is active if path name is equal to slash studio like this and we also need to mark this as use client in that case there we go so now it will say content here because we are on the studio Let's go ahead and let's remove the main section entirely. Let's remove the separator. And we can now fix the indentation here. Remove main section and personal section. And you can remove the separator for now.
Because this entire thing is much simpler, no need for us to separate that inside of its own components as we have to with the home sidebar, which clearly has different sections. But inside of here, we would just have content and exit studio, which should just lead you back to the root page. And if you click on studio here, you should go back to the studio. Great. So that seems to be working just fine.
Let's see. What do I want to do now? So I do want the sidebar, but I want it to be the separator, but I want it to be the separator but I want it to be here in between sidebar menu items. So just add back the side the components UI separator. There we go.
So we now have that. How about we go ahead and now create the header which will load the user Let's go ahead and let's prepare that component So we are going to put it inside of our sidebar menu here So it's going to be called Studio Sidebar Header and it will accept no props. Let's go inside of the Studio Sidebar and we are going to have to remove these two but for Now let's just create the Studio Sidebar Header.tsx like this. And let's export const Studio Sidebar Header. And return a div header.
Now let's go back inside of the index and let's import the studio sidebar header from .slash studio sidebar header and you should see the header text here. Now let's go ahead and let's remove the unnecessary main section and personal section from the studio sidebar. Again, make sure you're working within the studio module. Don't accidentally remove your home module. You can always confirm by going back to ensure that you have everything here.
Great. Now inside of here, Let's go ahead and continue developing the header. I think that in the beginning of this chapter, I told you that we're going to have to create the user's router. Well, that's actually not true. We don't have to.
We can, but we don't have to. We don't have to rely on the database record because we can use the entire clerk object here and clerk will actually have the most up-to-date data, right? Because every data change that happens in clerk will have to get resynchronized to our database. So we can actually safely just use this, user from use user from clerk next js that's it so this is what we are going to do we're going to go ahead and change this to be sidebar header from components UI sidebar and we are going to give this a class name of flex items center justify center and padding bottom of four and then we're going to add a link component from next link with an href of slash users slash current And then we're going to have to add a component called user avatar. And I think we have it user avatar.
Let's see. User avatar, We don't have it. Is it called Avatar? Maybe? It is.
It's called Components Avatar. Okay, so this is what we're going to do. We're going to go ahead and create this reusable component. Instead of Source Components, we're going to create User-Avatar.tsx, like this. And we are going to import the avatar from ./.ui-avatar or if you prefer components UI avatar because avatar exists but I want to create a reusable component for the user avatar so we can have proper tooltips and alt image and everything else Let's also import CN from libutils Let's also import avatar image here and let's also import cva from class variance authority and type variant props.
If you're wondering what this is, well if you take a peek at components UI and then pick anyone, for example, button. This is what it is. It's basically a util which allows us to create a different variance. And it's gonna be useful for us so we can create different sizes. I think it's just more manageable to work that way than using a bunch of ternary operations.
So for example, we can do const avatar variants, cw, cva. And in the first argument, you just give it some default classes, which I think we don't need. We don't need anything to be here by default because every class name is going to be different depending on the variant. So add variants and a variant size. By default we're going to have a height 9 and width 9.
Extra small is going to be height 4 and width 4. Small will be height 6 and width 6, LG will be height 10 and width 10, and then we're going to have extra large, which will be height 160 pixels and width 160 pixels. And then let's add the default variance, size and choose the default version. And that's it. We now have a nice way to control all the variants for our avatar.
Now let's create an interface. User avatar props, extends, variant props type of avatar variants passing the avatar url it can also be image url if that's what you prefer let's call it image url it will be a required string A name will also be a string. Last name which will be an optional string in case we want to modify something and on click will be an optional event as well. Let's export const UserAvatar here, like this. Let's assign UserAvatar props.
Let's get the image URL, name, size, class name and on click. And you can see how we have access to size, even though we did not add it here. That's because we extended the variant props, so it inferred all the possible size types. Great, so we have that. And what we're going to return is our avatar component, our avatar image, which will accept source to be avatar, sorry, image URL and an out of name and a plus name here will be CN avatar variance and we're going to pass in size and class name and an onclick.
There we go, reusable and modifiable user avatar. Great! Now that we have that, we can use the UserAvatar. So UserAvatar component here from our components UserAvatar like this. Let's go ahead and pass in some products.
So the image URL will be UserImageURL like this. Then we're going to have name, which will be UserFullName or user in case it's null. And we are also gonna have a class name here where we are going to add a very specific size. I know this kind of breaks what I just built there. I built all those variants and I ended up not even using a single one of them, but I prepared that for all the future use cases of UserAvatar, right?
And we are adding some transition when we hover here because this will have, it will not have an on click, it will have a link around it. Great! So now what we have to do is we have to do this. If there is no user, return null. We are going to add a proper skeleton in a moment but for now let it work like this and there we go we now have a nice little when you click here it leads to 404 because we didn't implement users current yet but as you can see it's already starting to look like what we imagined here so let's go ahead and go outside of this link here and add a div here with two paragraphs like this.
In the first paragraph we're going to say your profile and in the second paragraph we're going to say user.fullname like this. Your profile and then my name here. Let's go ahead and give this a class name of flex, flex column, item center and margin top of two. Let's give the first paragraph a small text and font medium and let's give the second paragraph a text extra small and text muted foreground There we go. This is looking much better.
Now let's go ahead and let's actually create a skeleton for this. So we're gonna go ahead and return sidebar header component. Let's copy the class name here so we don't have to write it again. And inside, we're going to render a skeleton from components UI skeleton. Like this.
It's going to be a self-closing tag. The first skeleton will represent our avatar. So let's give it the size of 112 pixels. And let's make it rounded. Below that, let's go ahead and copy this div class name here.
And we're going to pretend to have two paragraphs, which are going to be two skeletons here. The first one will have a class name of height 4 and the width of 80 pixels. And the second one will have a height 4 and the width of 100 pixels just to have a little variance between. So now when you refresh, okay it's looking good but we are missing something we are missing gap y1 here and let's also explicitly add gap y1 here as well. There we go this is looking much better.
Great! So now let's go ahead and just I would like to wrap this to make it a proper if clause. I don't like how it looks. One issue that we have, I believe, is that if I collapse this, it looks horrible, right? So let's just fix that as well.
In order to fix that, we can leverage our use sidebar from components UI sidebar. You can only use this if you are inside of a sidebar. So you have to render something inside of the sidebar like we do right here and then you can use this hook inside. So you can access the state and then you can check if state is collapsed. In that case you can return a different sidebar menu item here.
We have to import this, so let me just close it here. But I also added an import for sidebar menu item. Let's also add an import for sidebar menu button. And now let's go ahead and continue developing here. So we're going to have a sidebar menu button with a tool tip, your channel and as child, and let's actually call it your profile, because I will not be using the channel name anywhere.
This will be a link component to users current. And we're going to have a user avatar here again with image URL. Oops, this is a self-closing tag. Image URL will be user image URL, and name will be user full name, or user in case full name doesn't exist and size will be extra small. And then we're going to have a span here, your profile and a class name, text small, like this.
So we are basically simulating as if we had, you know, this collapsed state here. There we go. So we can now handle both of these cases and we also have a nice little loading here. I kind of don't like this jump here. I like to obsess over those details.
Let's see, can I do something about that? What if I added a gap 1.5 here? Or maybe two, Whoa. There we go. This looks much better, if you ask me.
No visible jump now. Great. So we just had this layout finished. I think the only thing that's missing here is the create button. So let's just go ahead and prepare it because it will be a little bit different.
It will be called the studio upload model. So we can prepare it, right? Let's do that. Let's go inside of components here and outside of the nav bar we are going to create studio upload model.tsx like this and we will export const studio upload model but we are not going to develop the model yet. We are just gonna add the button here.
So let's add a button from components UI button and let's add a plus icon from Lucid React and create. Let's give this a variant of secondary like this and I think that's it. Nothing else needed here but obviously we are going to continue developing here. Let's mark this as use client. So now let's go ahead and go inside of our studio, studio nav bar index here.
And let's go here where we render our out the button and let's render studio upload model like this, which we imported from ./.studio-upload-model and we should now have the create button right here. There we go. I think that is exactly as it was here. Perfect. And I think that is it.
Yes. In the next chapter, we're going to go ahead and actually develop the functionality. And before we can develop the functionality, we have to create the videos schema. We have to push those changes to our database. And then we can go ahead and create a new video when we hit the create button.
And then we're going to have to connect to Mux, which will be our video service, which is absolutely amazing. You're going to love it, especially with all the things that we are going to be able to do. We are going to give you a real YouTube experience. Our videos will be processing for some time, because they need to transcript and transcribe and all of those things. We will have subtitles.
We will even have a bunch of AI features. You're going to see it's going to be very, very cool. But yeah, you should now have a studio layout here which looks fairly different than the one from our home. Great great job let's just see did we do everything here I'm just gonna enable my pen here we created a studio route group and layout but we forgot to protect the studio route so let's go ahead and do that before I wrap up so instead of source instead of middleware.ts instead of aiming for slash protected we're going to aim for slash studio So now if I happen to sign out, let me just refresh and if I manually go to slash studio now, I should get redirected right here, sign in and if I actually sign in, I am redirected back to the studio because that's the last place I tried to access. There we go, we just finished that as well.
Great, great job.