So now that we have our webhooks ready, let's go ahead and let's actually start developing how our application looks like. So make sure you have localhost running. You actually don't need to have your ngrok running right now except if you're not going to be creating any new users. So from now on if you plan on modifying your user by changing the image URL or the username, deleting your user or creating new users you need to have your webhook running. So if you accidentally create one without it your database is not gonna be synchronized with your clerk dashboard.
So what you can do is go inside of Prisma Studio and delete everything you have in users and then go on to the clerk dashboard and manually delete all users and then try again. But for this part that we're gonna do now, we're not gonna do anything like that. So you can just have your localhost running here. And let's go ahead inside of the app and what I want to do is I want to modify my structure a little bit. So this page.tsx is going to be my main page but the thing is I want it to be a little bit different.
I don't want it to be standalone like this. Instead I'm going to go ahead and create a new folder called Browse and then inside I'm gonna go ahead and create another folder called Home and go ahead and drag and drop page.tsx inside of that Home right here. If this pops up you can click OK and now as you can see I have this weird unsaved file here so look at your open editors and you should see page.ts in .next slash types slash app, so if you have this just click on that and click save and you can close everything and make sure that you close this dot next folder, right? So that is just cache because we just moved our root page from one place to another. But because of the route groups, what does this page equal to?
Well, it equals to localhost 3000 Nothing has changed. It's exactly the same. We just put it in a couple of group folders so we can work better with it Great and one more thing I want to tell you so if you had some problems with that next cache with that weird unsaved file here's another way you can fix it. Go ahead and shut down your app so I'm gonna go ahead and shut down both my ngrok, I don't need it now, my Prisma Studio, I don't need that. And also shut down the app.
And what you can do is manually go ahead and remove .next and then do npm run dev again. And once you do that, .next will be created again. There we go. And everything should work just fine. So just in case for some reason you got stuck on that little page thing, it's not a big deal.
Great, so now that we have that structure, let's go back inside the Browse right here and what I want to create is a layout file. So go ahead and create layout.tsx and let's go ahead and give this browse layout let's go ahead and extract their children let's give them a type of react react node and let's go ahead inside render a div and inside very simply render those children and you should not be seeing any differences now because we are not using this layout in any special way great! So what I want to do now is I want to create a navbar component. So I'm going to replace this div right here to be a fragment and that is still going to be exactly the same. And then in here I'm going to create another div with a class name of flex-h-full and padding top of 20 so I'm adding a little bit space here on the top so our navbar has space and then here I'm gonna render the navbar itself and when we save of course we're going to get an error because navbar does not exist so inside of this browse folder create another folder underscore components like that and then inside go ahead and create another folder called sidebar and then create an index.tsx which is going to be the entry point for our sidebar.
The reason I'm creating this as a folder and not as a component like sidebar.tsx is because it's going to have a lot of elements itself. And I did not mean sidebar, I meant navbar. My apologies. So we're gonna work with navbar first. So go inside of navbar and let's do export const navbar.
And let's go ahead and instead of div, let's actually use the nav element and let's just write navbar inside and you can save this. Go back to layout, so inside of the browse layout right here and import navbar from .slash components navbar. And when you save, you should have a text navbar here at the top. Now let's go ahead and give this nav a class name of fixed like that. So it's always at the top.
Let's also confirm that with top zero, let's give it a full width, let's give it a height of 20, let's give it a Z index of 49, let's give it the background color of a specific hex code 252731, like that and let's give it a padding on left and right of 2 on large devices it's going to be padding of 4 let's give it flex justify between items center and shadow small like that. Great! So now we have our nav bar right here at least the skeleton of it. Perfect! Now inside of here I want to go ahead and create my logo component which is going to be similar to that Auth logo which we created recently.
So let's actually go ahead inside of our app, inside of Auth, in components here we have logo.vsx So let's copy that, let's go inside of browse and let's go inside of the navbar itself and paste that logo inside of here like that. Great and now we can import that logo from .slash logo and no I did not just randomly duplicate this so obviously it doesn't look good now as you can see but we're gonna modify it now but it is gonna reuse a lot of elements so it was better to just copy it, it was easier So make sure that you still have the logo inside of the out but what we did was copied it inside of the browse, inside of underscore components, inside of navbar, right alongside our index. We now have the exact same logo here. Great! And this is what I'm gonna do.
I'm gonna remove everything inside, right? But we're still gonna need this popins and we're still gonna need a CN here as well as the image. But one more thing we're gonna need is the link component from NextLink. And now let's go ahead and let's add link href to be slash like this let's create a div with a class name of hidden on mobile devices but on large devices it's going to be flex like this Let's give it items center, gap x4, hover, opacity dash 75 and transition. And now to actually see that element let's go ahead and create our little circle here.
So class name, BG, white, rounded, full and padding of 1. And then inside of here we're going to render our image so make sure you have image imported which is going to have a source of slash spooky.svg. So the same thing that we have inside of our auth logo component, right? So make sure you have spooky.svg inside of your public folder. Let's give this an alt of game hub, which is the parody name of our app.
Let's give it a height of 32. Let's give it a width of 32 as well. And I believe that's it. I think that should work. So you can see now on small devices, it's not visible.
But on big devices, it is visible right here. And when I click here, it's going to redirect us to the root page. Great! So now let's go ahead outside of this div right here and let's add another one which is going to have a paragraph of game hub and then below is going to have a paragraph of let's play and let's go ahead and fix this by escaping it with this appos right here so just replace that like this and I will just zoom out so you can see this there we go so you can see how now we have a text game hub and let's play below it So let's go ahead and style this even further. So I'm going to go ahead and give this view a class name to use cnFont.className.
Like that. So we have cn imported and our font is Poppins right here. So now both of our Game Hub text and Let's Play text are gonna share that same font. Perfect. And what I wanna do now is give the first paragraph a class name of Text Large and Font Semi Bold.
And the second one, text extra small and text muted foreground. Like that. Let's take a look at it and there we go. We have our nice little component here in the navbar and every time user clicks on that they're gonna get redirected to the home page because of our link component. Perfect!
Great! Let's go back inside of our index now and what I want to do next is create our search input. So let's go ahead and write search like this and if you save of course you're going to get a little error here. So let's go inside of the navbar here and let's create a new file search.tsx And what I'm going to do now is mark this component as UseClient because this one is going to be an input which is going to have onChange, it's going to have a useEffect, all of those things. And if you remember in my brief introduction in the server components and client components, all components which we create in the app router are server components by default.
So we have to explicitly tell this framework that we want this to be a client component. So let's do export const search. Return a view just saying search for now. Now go back inside of your navbar index and you can import that from dot, not from Lucid React but from dot slash search like this and now you should have a text which says search right here. Great and it's even visible on mobile here.
Great and let me just show you one quick difference which you can if you're not sure whether your component is client or server because you don't have to always add use client for example if our search component explicitly has use client written and then inside of here we add another component right that component let's say this is a new file, so another component.vsx, then that component does not have to have useClient at the top. So You don't have to write that. You can just do expert const another component, right? So that way, the reason that works is because everything that is inside of useClient is automatically gonna become client from now on, right? But there is a certain way that you can also render server components inside of client components and that's by using children.
So this is the way which where you can kind of inject a server component inside of use client and I have a feeling like this will just confuse you now So I'm gonna stop myself because we are gonna have these examples and they are gonna be clearer when you actually see the purpose behind them, right? But for now, here's one cool thing which you can do. So mark this as used client and add a simple console log which says I am logged here. And now when you take a look in your inspect element here and I will just filter this here so I am logged here. There we go, you can see right here that it is logged.
So I am logged here in my inspect element right here. But if I remove useClient, what happens then? Let's refresh. All of a sudden there are no logs here. But let's just add search here.
The component is still rendered. So where is this log? Well, it's inside of our terminal because this is by default a server component. There we go. You can now see the logs here.
I'm logged here, I'm logged here, I'm logged here. So that's how you can easily recognize whether something is a client or a server component if you're not sure. So for now bring back useClient for this component and let's go ahead and develop this. So what I wanna do now is add one component from chat-cn-ui, so do npx chat-cn-ui at latest add input like this. So let's go ahead and wait for this to be added and do npm run dev again and every time you shut down your app make sure to refresh your localhost here.
Perfect and oh I actually need one more package my apologies so also let's add npm install query string like that and we already have the button so that should be it for this component so refresh your localhost and let's go ahead and import QS from query string let's go ahead and import useState from react let's go ahead and import searchIcon and X from lucid react so it's important that you don't you can also import search from Lucid React but obviously it's going to conflict with our component name. So you can either rename this to like search input or you can rename this import to as search icon. Or what Lucid offers, at least for the version I'm using, is just directly using search icon. Like this. So that's why I'm doing that.
Alright, and let's also go ahead and import UseRouter from next-slash-navigation. So don't accidentally import it from a router because that's going to break your app inside of the app router which we are using. And now let's go ahead and import the input component from add-slash-components-ui-input and let's import the button component from add-slash-components-ui-button. So we have both of those. Input we've added just now and button we added in the beginning of the project.
All right, so now I want to replace this div with a form and I want to go ahead and give this form a class name of relative w-full on large devices we're going to use a specific width of 400 pixels flex and items center Let's go ahead and give it an on submit to just be an empty arrow function for now. And inside of here let's render our input component. Let's go ahead and give this a placeholder of search. So there we go, now you can see how we have a nice little search component here in the top and now what I want to do is I want to do the following outside of this input add a button component and inside we're gonna render the search icon great so now you can see how we have a big search icon next to this input so let's go ahead and style this a bit So give this search icon first a class name of H5W5 and text muted foreground. Now let's give this button a type of submit.
And I'm just gonna collapse all of the attributes. So a type of submit, a size of a small, a variant of secondary and I'm also going to give it a class name of roundedLNone like this. So I don't want it to have any borders on this side because I want it to flush with the input. So I'm going to have to do the same thing on the input and obviously I'm going to have to remove this kind of a ring or an outline because it just seems to clash with what I'm trying to do here. Alright, so now that we took care of our button, let's go back to the input and let's give it a class name of rounded-r-none.
So now when these two meet, their roundedness is going to be equal and it's going to look flush with one another. And now let's give it a focus-visible ring 0 so we remove that outline and we also have to give it focus-visible ring transparent and focus-visible ring offset 0 like that. So these three classes FocusVisibleRing0, FocusVisibleRingTransparent and FocusVisibleRingOffset0. So now if you try you can see how it looks very flush with one another and if you click here it's going to submit the form so it kind of refreshes but that's going to be fixed in a second. Great.
And now what I want to do is I want to add some functions here. So let's go ahead and let's add our router to be use router which we added an import right here then let's go ahead and let's add value and set value to come from use state and give the default value of an empty string let's add const on submit here Let's go ahead and give it an event to be react.formEvent HTML form element and let's do event.preventDefault so we don't refresh our page every time. If we have no value written and user submitted it, we're just gonna return, so no need to do any search if user hasn't written anything, right? And then let's do const URL is gonna be qs.stringifyURL, like that. And let's write url to just be an empty arrow function and query is going to give a term a value of value which we are storing inside of our state.
And let's also go ahead and add options to skip empty string to be true, like that. So things like this term are not gonna happen, right? So we're never gonna have a URL which ends like this because of this option. It's just gonna be like this, right? And now what we have to do is router.push and push that URL because a stringify URL is going to result in a string.
So it's going to look like localhost 3000 slash, sorry, not slash, but it's going to have this query term and then value which user has searched. So that's what we were pushing to the URL. All right. And now what I want to do is I want to add that to this input and to this on submit. So let's change this on submit here to use that on submit function which we just created.
And let's change this input to have a value of value and let's give it an onChange to get the event and use this setValue with a value of event.target.value very very simply And you can already try this out now. So look, my URL is localhost 3000 now, but if I write test and press enter, you can see that now my URL is slash term and it equals to exactly what I've written inside of my input. Great. So now I want to allow a user a way to clear their input by using this X icon, which we imported from Lucid React here. So first I want to define a function for that.
So const on clear is very simply just gonna call set value and empty it like that and then what I want to do is go ahead just below this input here and add if I have value go ahead and render that X icon like that so you can see that now I have this X icon I don't know if you can see it let me zoom in so now I have this X icon but if I remove it I don't have it and now let's go ahead and style this so it looks a bit better so we're gonna go ahead and give this X a class name of absolute top-2.5 right-14 h-5, w-5 text-muted-foreground cursor-pointer hover-opacity-75 and transition And let's give it on click, on clear. There we go. So absolute Top 2.5, right 14. So this is going to position it exactly in this place in the input where I want. So let me expand this a little bit.
So exactly where I want it right here. Then text muted foreground, that's what makes it the same color as the little icon here. The H5 and W5 give it the exact size that I want. Cursor pointer makes sure that when I hover on this it looks like I can click on it so it's not just a pointer right. And the hover opacity 75 means that when I hover it has a little bit of an opacity as you can see and transition just to smooth that out so it's not too stiff.
Perfect! So you can actually try and click this and there we go. You can see that now this is empty. And we are not going to clear the URL. So we're going to handle that differently and what I want to do now is go here and change this URL to actually lead you to slash search.
Because that's what we're gonna end up with later when we create the search page. Right now we don't have it, right? So we can't really test this out. So I'm going back to localhost. And if I write test, I'm gonna get redirected to 404, but my URL is correct.
So just ensure that your localhost 3000 goes to slash search and then uses that term and you can leave it like this for now we're going to work on that search page later but for now we have a beautiful and ready component to do that. Great! So what we have to do now is go back inside of the index right here and we have to create our third component which is going to be called actions so go ahead and render actions like this and if you save you're going to get an error So let's go ahead inside of the navbar and create a new file actions.tsx and let's go ahead and export const actions here and let's just return a div actions let's go back inside the index here and import actions from .slash actions like that and there we go you can see that now in here next to my input I have the actions right here. So let's go ahead and actually develop these actions now. So go inside of here.
And first thing I want to do is turn this into an asynchronous function. We can do that because this is a server component by default, right? So if we added useClient here, all of this would become client components, even if they don't explicitly have useClient at the top. That's what I was trying to explain to you earlier, right? Because you're gonna see patterns like that happen.
But since we don't do that then this is a server component. This is a server component. In search we explicitly tell it this is not. In actions we don't. So this is a server component.
So we can make it asynchronous and then we can get our current user from a wait current user from clerk next JS like that and now we have access to the currently logged in user so let's go ahead and do the following let's give this div a class name of flex items center justify and gap x2 ml4 but on a large ml0 like that a class name of flex-items-center-justify-and-gapx2-ml4-button-large-ml0 like that and then let's check if we don't have the user so if we don't have the user in that case go ahead and render sign in button from clerk next.js so add this import next to the current user and inside we're gonna add our button from components UI button so I like to separate my imports by adding the npm packages at the top and then this packages, which have the little at sign. And then below that, I would add like something which goes like this, right? Which doesn't have the at sign. So I separate them by spaces. So if you're wondering why I separated this.
Alright, so it's just a practice. It doesn't matter, right? I just like it that way. Inside of this button write login like this. And now what I want to do well actually I'll come back to this because we can't really see this right now because we are logged in, right?
So we're gonna have to come back to this. I want to give it a specific color, right? And now let's go here and check if we have the user so you can write the user like this but what I like to do is turn this into an actual boolean and in order to do that you can add double exclamation points so not one this will turn it into true or false, right? But this will turn this into a Boolean if the object exists, or if it is null, it's going to render this as false. And it is important to do that.
I mean, nothing dangerous is gonna happen, but you can get some bad user experience if you have a practice to not check what value comes before this end-to-end ternary. So I like to convert that to a boolean. And now in here let's go ahead and open a div with a class name flex-item-center-in-gap-x4 and let's again render our button component and inside I want to go ahead and add a link component from next slash link so make sure you add this import as well and let's add this and this link component and in here I'm gonna give it an href to be dynamic so slash you slash open template literals and write user.username like that. So that's why we needed our user to be loaded in full because we need the username. And now in here let's go ahead and let's add a clapboard a clapperboard icon from Lucid React like that And let's go ahead and write a span here, dashboard.
And let's go ahead and give this button a size of small, let's give it a variant of ghost, let's give it a class name of text muted foreground, hover text primary and let's give it an option as child so we can properly render this link inside. Great, and you can see how now we have this big dashboard button right here when we are logged in and we are logged in because we can we can right see this so great Let's go ahead and continue developing this. So this clapperboard is gonna have a class name of H-5W-5 and on desktop or large devices, it's gonna have a little margin here. And the reason it's only gonna have this margin on desktop and not on mobile is because this will be hidden on mobile and only appear on large devices, right? So you can see how now it is just a clapperboard icon, but when I expand and zoom out a bit, it becomes, as you can see, a nice little dashboard button.
And when you click on it, it's gonna redirect you to 404, but the URL should be slash you, and then the last username which you modified when we practiced our webhooks of the currently logged in user. Great, so now we have that. And what I want to add beside it is the user button. So that's why I've wrapped this inside of a div so you can hold the two elements side by side. So after this button here, let's go ahead and render user button from clerk next.js so make sure you have the sign in button make sure you have the user button and the current user imported from clerk next.js and let's go ahead and give it an important prop after sign out URL to lead to the slash so it stays on our page here.
So let's go ahead and look at this now and there we go you can see how now I have the control over my user right here and I also have this button here perfect so we finished our navbar what we have to do now are two things so first thing that I want to do is revisit our middleware right so make sure you save all of this make sure your navbar is nice and here and now go inside of middleware and besides having this this API webhooks I also want to enable all users whether they're logged in or not to visit my root page right so just like on real twitch I don't care if you're logged in or not, I want you to be able to see videos. But you're only gonna be able to stream videos if you're logged in. Great, so when we added this, it's also, it would make sense that we go back inside of our app inside of browse home page.tsx and we no longer have to render this user button here. Instead I'm just gonna call this home page. Like that.
Great! So now if we try it out and refresh this and go ahead and sign out. There we go! Now I have just a login button here and that's what I want to do next. So I want to go ahead and modify this button so it has a specific style that I want which we're going to reuse a couple of times in this project.
So let's go ahead inside of the components UI button right here and go ahead and find the button variants here and find the last one which is link and now let's add one which we're gonna reuse throughout this project. So let's write primary and that's gonna use the text primary BG blue 600 and on hover it's gonna be BG blue 600 slash 80 like that. So that's gonna be our style here. So let's go ahead back inside of our app folder, browse components navbar actions.tsx right here. And I told you that we're gonna get back to this button so find this one which is wrapped inside of sign in button and let's go ahead and give this button a size of small and a variant of primary which you should now have access to and there we go now this button looks much better and when you click on it you will be redirected to our custom sign-in page and we didn't even have to explicitly tell it where to redirect.
That's because this sign-in button comes from Qlerk and inside of our environment file we've pretty clearly told Qlerk what is my sign-in URL so it knows exactly where to redirect us. Great! So I'm gonna go ahead and log in now to confirm that this is working. Great! So just make sure you didn't accidentally create a new user now if you have multiple email addresses.
I mean if you did it's it's not a big problem right we're gonna reset our entire user database and all of our clerk users a lot of times in this project because we're gonna keep adding some new models so we have to make sure that we have a clean slate. So just don't worry for now if you accidentally did that because we don't have ngrok running meaning that our webhook is not working if any events are being fired. But it doesn't matter because right now we're just focusing on the navbar here. Great so this is working I have my dashboard button we have our search right here but one thing that I feel like it's missing is when I collapse there is no way to redirect to the home page, right? So I feel like we shouldn't hide this entire Game Hub parody logo but instead we should just show a little element here.
So let's try and develop that. So I'm gonna go inside of logo here. Let's see if that's gonna work and I'm not gonna hide this, right? I'm not gonna hide the entire thing. So instead of hidden an LG flex here I'm just gonna write flex.
So now I should see the entire thing here and I do. And instead I'm gonna give this div which renders the text that dynamic thing. So let's go ahead inside of this CN here. And in the first paragraph I'm gonna give it hidden by default but an LG use block like this and there we go now I have a little icon here so what we can do is separate these two items a bit right so we can do that by adding gap x1 or 2 is it what Which one? Oh we already have GapX4.
Alright we're gonna do it like this. We're simply gonna go inside of this div which holds our image and give it a margin right of 2 like that and let's also give it a shrink of 0 can I do that? So maybe margin right of 4 5 8 10 maybe 12 Alright, 12 it is and this is only gonna be visible on on small devices so that means that I have to add on LG MR is 0 and on LG shrink is just shrink I think I can write it like that so when I expand, there we go. Now it looks better. Now our mobile view clearly supports this button here as well.
Perfect. So let's just actually try it out in mobile to see if it works and there we go it looks good. Great. We can now click on this, we have our search, we have our user dashboard. So and this is the iPhone SE is the smallest you should aim for, right?
So it makes no sense to go even smaller than that because this device doesn't exist. So I always try to go to iPhone SE, even though I think this is maybe unrealistic example. I think most people have larger phones. So you can even use like this ones and it looks good. It looks good enough.
Great, so we finished our navbar and now we are ready to start developing our sidebar which is gonna be collapsible and it's going to render a list of recommended users for both logged out and logged in users. Great, great job.