So what I want to build now is the creator dashboard and we already have something prepared for us here. So I want you to go inside of the app folder, browse, components, navbar and in here you have the actions component. And in here we have a couple of things. So we have our login button if we are not logged in But if we are logged in we render the user button and alongside user button We render one button with an icon clapperboard which says dashboard on desktop and it redirects to slash you and then the username of the currently logged in user. So that is this button in the navbar right here which says dashboard.
So I'm currently logged in as the username something else and when I click here you can see that in my URL, I am on localhost 3000 slash you slash something else. And it's a 404 page because we don't have that developed yet. So you can already kind of imagine the structure we need to build this page but I've also prepared a little diagram here. So it's supposed to be something like a U folder and then dynamic username folder and then page.tsx and that's absolutely true but what I'm gonna do is do exactly that but I'm also gonna add another route group inside of it called home so I can keep my page.tsx in a separate folder because then this home folder will also have some components, some loading states, maybe even its own layout. So it's better for it to be like this rather than just cluttered around in the username folder where it doesn't belong.
And this route right here, I mean this folder structure is going to equal this localhost which we just saw a second ago. So let's go ahead and let's close everything here and what I want to do is I want to go inside of the app folder and first I want to create another top level layout group called dashboard. So go ahead and create a dashboard route like this and then inside create another folder called you and then another folder dynamic username like that and then inside we're gonna create another route group called home and then inside page dot TSX So the reason I created this home is because now inside of here I can create a new component called new folder called components Which are only going to be components used for this exact page because later This username page is gonna have another set of subroutes like keys, chat, community so it makes sense that we also hold the initial route inside of its own folder but I also don't want this home to be part of the URL it would just be weird so this way we took care of that. So let's go ahead and create this page so I'm gonna call it creator page and just return a div creator page like this so if you have your actions properly so this actions in the browse components navbar actions which lead to slash you slash username from the current user here you should be seeing this creator page now when you click on the little dashboard icon here at the top or if you are on mobile it's just gonna be the clapperboard icon.
So when I click here, there we go, it says creator page and I'm redirected to the correct URL. Great! So I'm gonna leave this creator page to be as it is now And what I'm gonna do is I'm gonna go ahead and create a little useful lib for us in the out service. So go inside of the lib folder out service and the same way we did getSelf, I'm gonna go ahead and I'm gonna write export const getSelf by username, like that. And go ahead and write an asynchronous function which accepts the username which is a string.
Let's go ahead and getSelf using the function awaitCurrentUser So we already have this from a clerk above. So await current user. And then what we're gonna do is we're gonna check if we have that current user. So if there is no self, or if there is no self.username, throw new error, unauthorized, like that. And then let's go ahead and fetch the actual user by this username from our database.
So await db.user find unique where we have a matching username. If there is no such user throw new error. User not found. And if we do have that user we still have to check if that's the currently logged in user. So this function is not the same as our user service where we have get user by username.
So this is to load any user at all but this specific service is to load our creator dashboard using our username right because that's the structure of our URL And if you're wondering why did I even choose this URL structure? No, that's because I looked at other live streaming websites and how they handle that and this is the solution they use. We could have technically just do something like slash dashboard or slash creator but one thing that this could allow in the future is modularity, right? So for example, if you were to continue developing this platform and maybe enable some moderators, right? Which can modify specific username, specific users stream and settings.
So this way you're gonna allow them to be admins of multiple users because our URL uses the username for the creator dashboard. So that's one pro of this solution. The con is obviously that we have to do this bit complex query here. So now that we confirm that we have our user make sure you are in the out service working on the getSelfBy username. Now let's go ahead and check if self.username is equal sorry is not equal to user.username.
And if that's the case, throw a new error, unauthorized. So we are never gonna be able to look at someone else's creator dashboard and then just return self, Like this. Or we can actually return the entire user from the database. That might be even better. Like that.
Great. And now what I want to do is I want to go inside of the app folder, inside of dashboard and I want to go inside of the username here and I want to go ahead and create layout.tsx so not inside of the home folder but inside of the username folder here and let's go ahead and create this creator layout here And let's just go ahead and give this layout all the props it needs. So interface creator layout props is gonna have params which hold the username, which is a string and the reason we have that is because this is a server component by default and it's inside the dynamic username folder, meaning that it has this in the params. And since this is a layout file, we also know that we have the children, so we can add that as well and then we can just easily render the children inside and of course let's go ahead and actually add this props so creator layout whoops whoa creator layout props and let's extract the params and the children And now you should be seeing the creator page just as we did a couple of moments ago.
But now what we can do is we can go ahead and write const self to be await get self by username from our libauth service here and in here we can pass params.username and we have to turn this into an asynchronous function as well and then let's go ahead and write if there is no self redirect from next slash navigation to the root page like this. There we go. So if we are trying to be any other user we're gonna get an error from this function or if by any chance we don't get an error from the function but we don't get self as an object, we're just gonna remove the user from that page. So this way only ourselves can load the creator dashboard for our username. Anyone else who tries will get redirected and get thrown errors.
And we are later also gonna create a proper error page for this. So now let's go ahead and let's actually style this thing so it's not gonna be a div it's just gonna be an empty fragment and let's go ahead and wrap this inside of a div inside and let's give this div a class name of flex-h-full and padding-top of 20 so now you can see that our creator page has some space here at the top and that space is going to be for our navbar so what we can do here is we can copy the navbar from our existing browse component here. So go into browse components and you should have the navbar. So let's go ahead and copy this entire folder here. And now let's go ahead inside of the username and create another folder underscore components and you can just paste the entire navbar folder so you should have the actions index logo and search and now go ahead and let's render the navbar above it from dot slash components navbar because we now have them in this components and now you should have the exact same navbar as you have on your other page but of course we're gonna modify it a bit because we don't need the search page and we also need an exit button from here.
So let's go ahead and do the following. Go inside of this newly created navbar and go inside of the index here and at first I want to remove the search and I want to remove the search import. So no search for this one. So now your search should disappear from here, right? And what you can do now is also remove the search component.
Make sure you don't accidentally remove it from the browse. So just close browse. Make sure you are inside of dashboard, inside of username components, and go inside of here and remove that search and you refresh your page to ensure you're not getting any errors. And now let's go back inside of here and first thing I want to do is I want to modify the logo a bit so let's go inside of the logo here and let's modify it so it doesn't say let's play here instead let's write creator dashboard so now when you expand to desktop mode you should be seeing the creator dashboard right here great and what we have to modify next is the actions component. So let's go inside of the actions.
Of course the copied actions, right? And this is what we're gonna do. We're gonna remove this await current user. We can remove the async from the actions. And we no longer need this login button because this page will only be visible for users who are already logged in.
So this is what I'm actually going to do. I'm just going to remove everything from here. I don't need anything at all. And let's start from the beginning. Let's create a div here with the class name, flex-item-center, justify-end, and gap-x2.
Then inside let's render a button component and let's just simply add a link inside with an exit and let's write an href to go to the root page and let's add a logout icon from Lucid React. So make sure you import logout. We can remove everything from clerk here for now and we can remove the clapperboard. Like this. And now you should have this exit button here.
So now what I wanna do is give this button a couple of properties. So give it a size of small, a variant of ghost, a class name of text muted foreground and hover text primary. And let's give it an as child property so it properly handles this link and let's give this logout URL a class name of h5 width 5 and margin right of 2 and outside of this button let's add the user button from ClerkNext.js so make sure you import ClerkNext.js user button here and this is gonna be a self-closing tag and let's give it a property after sign out URL to go to the root page like that and now as you can see we have our new dashboard here so I'm just going to refresh this page and there we go now this says creator dashboard when I click on the exit I am back on the home page and I have my recommended users but when I click on the dashboard here there we go I am on slash you my username which I can confirm from here as well and now we can go ahead and continue developing this. So now let's go ahead and let's create the creator sidebar right here.
So for that we have to go back inside of our username so inside of dashboard you username layout here and just above the children let's go ahead and let's add the sidebar component and make sure you don't import this from anywhere because it should not exist so don't accidentally import the existing one from app browse components we're not going to use that one it's gonna be similar but we're not gonna use it so now let's go ahead inside of the components and create a new folder sidebar and inside of here create an index.tsx and let's export const sidebar from here and let's return a div sidebar. Now go back inside of your layout.tsx and you can import the sidebar from ./.components sidebar. So just as we did with the navbar. And when you save you should have this sidebar here on the side. Great!
So what I want to do now is I want to go ahead and create a store for our creator sidebar so we can collapse this as well, right? So for that I'm going to close everything and I'm going to go inside of my store folder and I will copy and paste the existing use sidebar and I'm going to rename it to use-creator sidebar like this. So make sure you have this one and then let's go ahead and replace all instances of sidebar with creator sidebar like that. So it's going to have all the exact same props. So we have the create sidebar store.
We're going to call it use creator sidebar and in here we are using create sidebar store. Everything else stays exactly the same. Right? So now what I want to do is I want to go back inside of my app folder dashboard, you, username, components sidebar in here and we're going to go ahead and we're going to replace this div to be our wrapper component, right? So we already have one but we're not going to reuse it because we're going to create a new one here so go ahead and create the wrapper.tsx component inside and go ahead and mark it as useClient and now let's import CN from libutils and let's also import useCreatorSidebar from store.creatorSidebar.
Let's create an interface wrapper props here which is going to accept children which are react.reactNode. And Now let's export const wrapper here and let's assign the props wrapper props. Let's go ahead and extract the children from here. Let's go inside and let's return an aside element. Let's give it a class name which is going to be dynamic.
So first I want to give it classes of fixed, left, zero, flex, flex call, width of 70 pixels by default on large devices is going to be 60 which is 240 pixels, age full, BG background, border right and border of a specific hex color 2D2E35. And let's give it a Z50 index. And now inside let's just render the children like that. Now go back inside of your sidebar index component and import that wrapper from .slash wrapper like that. And now as you can see we have a nice little sidebar and you can see how on mobile it is collapsed and on desktop it is big.
Great! So now what I want to do is I want to enable that hook to also be part of the CSS here. So let's go ahead and extract collapsed from use creator sidebar. Let's get the entire state here. And now what I want to do is add dynamic if it is collapsed.
In that case, we're going to go ahead and fix the width to 70 pixels just as it would be on the mobile mode. Great! So now that we have our wrapper what we have to create next is our toggle component. So let's go ahead inside, well first let's add it here. So I'm gonna add a little toggle component and don't import this from anywhere.
We're going to create a new one. So inside of the new sidebar folder, create a new file toggle.tsx. Let's mark this as use client and let's go ahead and export const toggle like this and let's return a div saying toggle. Let's go back inside of our new sidebar index and let's import toggle from .slash toggle. Now go back inside of the toggle component and let's go ahead and develop this.
So first thing that I want to do is I want to go ahead and extract everything I need from use creator sidebar. So make sure you import this and make sure that toggle component has use client let's go ahead and get the entire state and return it and now let's extract everything we need so we need collapsed we need on expand and we need on collapse here Now let's go ahead and write the dynamic label. So label is going to be ifCollapsed, it's going to say expand otherwise collapse. So just as it is in our toggle component in our main sidebar on the home page, right? So it's going to be very similar to this one.
You probably could have found a way to reuse that, but I just find it easier to write it twice in this case. I don't think it's such a big deal. It gives me more clarity and gives me more control if I want to customize it even further later in the future. So this is what I'm gonna do. I'm gonna replace these divs with a fragment.
And first thing I'm gonna write if we are not collapsed. Actually, let's start with collapsed. So if we are collapsed in that case let's go ahead and open a div with a class name with full hidden lg flex items center justify center padding top of four and margin bottom of four And inside let's add our hint component which we already have developed. So make sure you import the hint component in here. Let's pass in the label to be label from our constant here above.
Let's give it a side of right and let's give it as child property. And inside let's open up a button component. So make sure you import button from add slash components UI button. And let's go ahead and give it a couple of attributes here. So I'm going to give it on click to be on expand I'm going to give it a variant of ghost I'm going to give it a class name of H auto and padding too and inside I'm going to render arrow right from line from lucid react So make sure you import arrow right from line from Lucid React here.
And I just want to give this little icon a smaller size by using the height of 4 and the width of 4. Right, And this should now not be visible anywhere, right? But if you go inside of your store, inside of Use Creator Dashboard and change the collapsed to be true by default and expand it to desktop mode you should be seeing a little icon here, right? And this icon is a toggle button. So the same thing that we had previously, right?
So now change this collapsed from true back to false in the Use Creator sidebar and let's go back inside of our new toggle component. So make sure you're developing inside of Dashboard, You, Username, Components, Sidebar, Toggle right here. Great! So now we've finished the collapsed state and now let's go ahead and let's create the not collapsed state. So if we are not collapsed, go ahead and create a class name padding 3 PL 6 margin bottom of 2 hidden LG flex item center and width full.
Inside create a paragraph which says dashboard and let's go ahead and give this class name font semi-bold and text primary And below that let's open up a new hint component here. Let's go ahead and pass in the label to be label. Let's pass in a side to be right and as child property. And then inside let's render our button component which we already have imported because we just used it a couple of moments ago and Inside of here. I'm just gonna prepare a couple of attributes so I'm gonna give it an on click to be on collapse and I'm gonna give it a variant of Ghost and I think I misspelled this variant like that and let's go ahead and also give it a class name of HAuto padding 2 and MLAuto and let's render arrow left from line from Lucid React like this so make sure you import this as well so this is the not collapsed state finished now and I believe that if we expand there we go we now have our dashboard here like this.
Great! So now it seems like we just have to modify the actual functionality but this one seems much larger than the one below. Let's see if we misspelled something here. Yes, I forgot to give classes to this one. So go inside of the arrow left from line and give it a class name of H4 and width 4.
And now they should be appropriately the same size. Like this. Great. So now what we have to do is we have to enable our wrapper to actually change the sizes because it seems like it's not doing that. So I want to go ahead and revisit my wrapper component.
So go inside of the sidebar folder inside of your new wrapper component here and in here It seems like we are using the collapsed state here, but for some reason this doesn't seem to be working so let's go ahead and see why that is happening. If I go ahead and write it like this will that work then? Let's try it out. So if I go here and click on this now it seems to be working. So it seems to be because of these default classes which I have right here, right?
Let's take a look here. Yes, it seems to be because of that. So let's go ahead and do this. I'm just gonna make it like this on default. So in my new wrapper component make sure that the default size is w60 like this and that should fix this issue right so when I'm collapsing here this seems to be working just fine.
Alright and now what we have to do is an equivalent container to push our content on the side when we are expanded and when we are collapsed. So let's go back inside of our dashboard layout here or creator layout, right? And let's wrap the children inside of a container component. So don't import this from anywhere, it does not exist yet. We only have the one from the home layout, right?
But this is a different layout. Let's go inside of the components and let's create a new file container.tsx like this. And let's go ahead and mark this as useClient and let's go ahead and let's import use effect from react let's go ahead and let's import use media query from use hooks TS let's go ahead and import CN from libutils and let's go ahead and import use creator sidebar from our store and now let's create an interface container props to accept the children which are react.react node and now let's export const container let's assign the props to it so container props and let's go ahead and just simply let's do a return div which renders the children right And now let's just go ahead and extract the children from the props. And now we have our container component. Go back inside of your Creator layout and import that from ./.components container.
So this is the imports you should have navbar, sidebar and the container great and now you should no longer be having any errors here so let's go inside of the container now and here's what I want to do first I want to go ahead and I'm going to import everything I need from use sidebar, use creator sidebar so let's get the entire state here and let's extract the collapsed state on collapse and on expand like that and then let's go ahead and let's use the use effect here. Actually, let's also add const matches to be useMediaQuery and let's use maxWidth 1024 pixels. So now we're going to do that automatic collapsing and expanding of the sidebar if the user changes from desktop view to mobile view using the use effect. This is the same thing that we did inside of our first container component. So if it matches on collapse else on expand And now let's add all the necessary dependencies which are matches onCollapse and onExpand.
Like that. And now I want to go ahead inside of here and write a class name to be dynamic. Flex1 by default and then if we are collapsed ML is going to be 70 pixels. Otherwise ML is going to be 70 pixels on mobile but on large it's gonna be 60. Meaning that it's gonna be expanded.
Great! So let's go ahead and try this out now. So in here when I click collapse, there we go, the content is being pushed. If I am on collapse mode in desktop and then go to mobile mode it collapses back. Great!
So now let's check this out if we have any hydration errors. So this seems to be not having any hydration errors for now but I don't like the flickering on mobile mode. So let's go ahead and see if there is a way for us to improve this. So I'm gonna go inside of the app folder, dashboard, components, sidebar, inside of the wrapper here and in here we already attempted to do something like that. Let's go ahead and give it one more try.
So I'm gonna give it a default value of 70 pixels. On large it's going to be 60. And then inside of this collapse I'm gonna change this to also use the large. I'm just gonna add a prefix for this to be large like this. And let's say that fixes the flickering and it seems like it does, right?
So when I go here and refresh there's no flickering. If I go back here there's no flickering either. Perfect. So exactly what we wanted from here. Great!
And now what we have to do is we have to create our navigation and this one is going to be a bit simpler because there is nothing to load. Usually we have to load our recommended users and stuff like that but in this one that's not the case. In this case we just have the finished navigation hard-coded right? So let's go inside of the index here and below the toggle add the navigation component and now let's go ahead and create that component. So go inside of the sidebar and create a new file navigation.tsx.
Let's mark it as use client and let's export const navigation like this and let's return a div navigation. Let's go back inside the sidebar index and now you can import navigation not from Lucid React but from .slash navigation and when you save you should no longer be getting any errors and instead in your desktop mode you should see the navigation here also on your mobile mode but it should overflow like this. And now let's go ahead and let's actually create this. So I want to import a couple of stuff here. So let's import useUser from clark-next.js.
Let's import usePathName from next-navigation. And let's go ahead and let's import the following icons from lucid react so we need full screen we need key around we need message square and we need users like that Now let's go ahead inside of the navigation here and let's write a path name. We use path name. Let's extract the user from use user and now let's go ahead and let's render our routes which is going to be an array of objects so each array is going to have sorry each object is going to have a label for this one it's going to be stream and the href for it is going to be dynamic so open backdex slash you and then user username and make sure you use this ternary right here and icon is going to be full screen like that. And now let's go ahead and copy and paste this and the one below is going to be our keys and that's going to lead to the exact same URL with slash keys at the end and it's going to use key ground.
Let's go ahead and copy that one as well. This one is going to be chat and it's going to lead to slash chat and it's going to use the message square icon. Let's go ahead and open this again. And the last one is going to be community which is going to lead to slash community and the icon is going to be users which we already have imported so this four icons here. Great!
Now that we have that let's go ahead and let's change this from a div to an unordered list and inside I want to go ahead and do routes.map get the individual route and for now let's just render a div with route.label and passing the key to be route.href like this and there we go now inside of our sidebar here we should have stream keys chat and community So now let's go ahead and give this unordered list some styles. So I'm going to give this a space Y2, PX2, PT4 and an LG PT0. Like that. Great. So now it looks a bit spaced out and now what we have to create is our nav item component.
Right? So we're going to replace that with the nav item component. So let's go ahead and do that here. So nav item. Let's go ahead and give it a key of route.href like that.
Let's go ahead and pass in the label from route.label Let's pass in the icon from route.icon. Href from route.href and let's give it isActiveProp which is going to be a simple comparison of path name and the route dot href so we know that we are on that page and if you save you should get an error because nav item does not exist So let's go inside of the sidebar and create a new file navitem.tsx like this. Let's mark it as useClient and let's create an interface navitem.props from here. Let's give it an icon to be a type of Lucid icon from Lucid React. Let's give it a label to be a string an href to be a string isActive to be a boolean and let's export const navItem and let's just assign this props here so navItem props Let's extract the icon, the label, the href and is active.
And now let's go ahead and let's render this, right? So I also want to include the collapsed state from use creator sidebar. So return the entire state here and make sure you imported the use creator sidebar like this. And now we can dynamically render our button component. So make sure you import button from components UI button and Then let's go ahead and give this some attributes.
It's gonna have as child attribute the role Well, we don't need the role. The variant is gonna be ghost class name is gonna be dynamic so make sure you import CN from libutils here. Let's go ahead and pass in some default classes, which are going to be full width and height of 12, and then if we are collapsed, justify center, otherwise justify start. And if we are active, in that case, bg-accent. So it shows that we are currently on that page.
Now inside of here, render a link component from next-link. So just make sure you add an import for that as well. Let's go ahead and let's pass in the href to be href prop. Let's create a div inside with a class name of flex item center and gap x for. Let's render the icon and yeah I didn't explain where we get the icon from so we pass in the icon as a prop here and the icon looks like this so users full screen right?
So what we have to do is we have to turn this prop into an icon with a capital I and we can do that by an alias. So let's go ahead and give it a colon and then icon with a capital I. And now we can use it as a component. So go inside and simply render that icon. And let's go ahead and give this some dynamic class names as well.
So in here write h-4 and w-4 and if we are collapsed, go ahead and give it a margin right of 0, otherwise a margin right of 2, like this. And then lastly, go ahead and write if we are not collapsed only then also render the label inside of a span. Like this. Great! So go back inside of the navigation and you can now import that from .slash nav items.
So make sure you add this component. And you should no longer be having any errors. As you can see now we have a beautiful navigation here. It's collapsible and you can see how it looks on mobile without the toggle button you can still access everything you need from here. Perfect!
So it's very similar to what we had right here, right? But now it's here. But there is a loading state that we have to take care of here and that is this state right here inside of our navigation components in here we have use user but use user can either be the user or null right so that's because this use user hook from Clerk has a loading state. So this is what we're gonna do. We're gonna go inside of the nav item and we're gonna create a skeleton from it.
So let's go ahead and let's write export const navItemSkeleton here and let's return a link element, sorry a list element and give it a class name of flex-items-center-gap-x4-px3-py2. Let's add a skeleton component from components.ui.skeleton So just make sure you add this import. All right, let's give this skeleton a class name of min height of 48 pixels and a min width of 48 pixels as well and rounded medium. So that represents our icon and then create a div with a class name of flex1 hidden on mobile but visible on desktop and inside render a smaller skeleton which is simply going to have a height of 6. Like this.
Great! And let's try it out now by going back inside of our navigation here. Before we render this, so make sure you're inside of the navigation component, We also need to check if the user has actually loaded. So we can do that by checking if we don't have user.username in that case let's go ahead and let's return an unordered list with a class name space y2 and let's go ahead and render array of four items dot map skip the first one, get the index and render nav item skeleton which we just created and give it a key of that index. So make sure you import nav item skeleton from .slash nav item.
And now I don't even think if this is going to be visible. You can see actually it is visible. For a split second it is visible because that's the time it takes to load our user. And let's check this on mobile. And there we go.
We have no hydration errors it seems. Everything seems to be working fine. So we had some hydration errors here. Looks like because of that suspense component but in here we don't have it at least for me we don't but then again even if you're having it I don't think it will break the page entirely so I'm not having it this seems to be working just fine so great this is gonna be the end of this module and what we're going to do next is create the actual stream model inside of our database and then we're going to create the settings for all of these things inside and then we're finally going to create our live stream page. Great, great job!