All right, so before we go on to creating the results search page, I want to go ahead and fix a couple of issues that I have here. So first things first, I want to give you a quick reminder that if when you start streaming, you're not seeing the user become live. So I'm going to refresh and there you can see how my user just became live. If you are not seeing that, make sure that you have your ngrok running, right? So you need to have this URL running, whatever it is, or you can run ngrok HTTP 3000 and make sure that in your live kit in webhooks you have that exact URL.
If you change the URL you can click edit endpoint and then modify it just remember to leave the suffix at the end. So I just wanted to give you a quick reminder of that. So go ahead and start a live stream and ensure that you can see the live stream in here and ensure that you have a live connection at this side here. And now I want to go ahead and just modify my result card a bit because I've noticed a couple of issues. So let's go inside of the Browse Home Components result card.
And first thing that I've noticed is inside of thumbnail here we have a little issue and that's that I've wrote group hover minus translate minus one so change that to minus two that's how it should look like and I think in the video I even said two but I wrote one. And the other thing is this. So this actually isn't even showing anywhere. That's because this should be inside of the thumbnail. So go inside of your result card and copy this like that and remove it from here and then go inside of thumbnail and go in here to your main return function where you render the content dynamically and below it render this and change this from data is live to just is live and import live badge in here right back and I'm gonna change it to components like this and now as you can see in my view here there we go we have a nice live badge in the corner.
So that's what I wanted to showcase. So make sure that you are live somewhere and you should have a nice little indicator. And I believe that even if I remove my thumbnail now, so let me just go to another user here. I'm gonna go ahead and remove my thumbnail Like this I want to see how that looks like now, so if I remove my thumbnail I have Live in the corner, and I have live here if you like it like that you can leave it but I have a feeling it's a bit too much, right? I think one indicator is enough.
So what I'm gonna do inside the thumbnail component I'm only gonna render this if it is live and if we have source. Like this. So only if we are live and if we have if we have a thumbnail right because if we don't have a thumbnail we're going to show the badge on our user avatar right so no need for that great so we resolve that issue now inside of the result card I think I've noticed one more thing. So go inside of the result card. I misspelled truncate.
So make sure it's properly named. And now you can see how I have like a little three dots at the end here. Great, and what I wanna do now is I wanna resolve this little potential security issue that we have with this page right here where we render our actual stream. So go ahead and make sure you are live because I have a feeling it's gonna be easier to confirm that everything is still working. So for this screen we use our lib user service right specifically get user by username So we use the get user by ID as well and we include the entire stream but that's okay because if you search where we use it we only use it in server actions and that's not in the javascript bundle so that's in a completely different encapsulation so we don't have to worry about spilling any secrets there.
But inside of getUserByUsername it's kind of dangerous to include the entire stream because we have some sensitive information there. So what I'm going to do is the following. I'm going to change this to not use include but instead it's going to use select and I'm gonna go ahead and now we have to select everything we need from the user so we need the ID of the user, we need the username of the user, we need the bio of the user, image URL of the user and I think those are all the information we need from the user and now for the stream we can go ahead and pick exactly what we need. So we also need the ID from the stream, we need isLive, oh and make sure you do another select inside of the stream. So we need ID, we need is live to be true, we need is chat delayed, we need is chat enabled, we need is chat followers only, we need the thumbnail URL and we need the name of the stream.
So there we go. Now we don't load all of those sensitive information like stream key and ingress ID. We only load very specifically exactly what we need. And now what we have to do is we have to go back inside of our StreamPlayer component so we can modify the types because okay this seems to be working now and I think that if I refresh this page everything is still working yeah everything is still working but here's the thing we have some type errors in our components now so inside of our app folder browse username page.tsx We should have an error here because we're passing an invalid model of stream and player because you can see in here in the stream player props we expect the entire thing. So this is what I'm going to do.
I'm going to create a type custom stream which is going to have an ID, which is a string, isChatEnabled, which is a Boolean, isChatDelayed, which is a Boolean, isChatFollowersOnly, which is a Boolean. We're going to have isAlive, which is a Boolean as well, thumbnail, URL, which is a string or null, because it's optional in Prisma, and name of the stream. And then we're also going to have a type of custom user. And let's put an equals sign here. It's going to have an ID, it's going to have a username which is a string, bio which is string or null because it's optional in Prisma, stream because it's going to be custom stream or null as well because it's also not required and image URL which is a string and lastly we're also gonna have the count followed by which is a number and then we can modify the stream player props so instead of stream it's going to be custom stream like that.
And instead of user is going to be custom user like this. Great. And it seems like I've resolved all of the issues here. And it looks like my entire thing here is still working. Let me try in my creator dashboard here.
So in here, we seem to be getting an error. So what we have to do is go ahead and see what's going on inside of app folder dashboard, you username homepage.vsx. Uh-huh, so we also need the external user ID it seems. Okay, so let's go inside of get user by username and let's add external user ID to also be selected and then we no longer have any errors here and if I try and refresh in my creator dashboard there we go. The entire thing is working let's write a test message here to confirm.
So we have slow mode on, so after 3 seconds it's here and it received here. Great and let's go ahead and see if there are any issues with my follow and unfollow. So this seems to be working fine and now I am blocked perfect, so we resolved this little potential security issue in our stream player we are now only sending the information that we actually need, we have no type errors inside of our app and now we are ready to create our search page. So for now we can stop streaming because we're gonna go ahead and create this page where we currently write something it leads us to a 404 not found. So we're gonna go ahead and build this now.
So this one will not take as long because we can reuse some components like our thumbnail. So let's go ahead inside of our app folder inside of browse let's create a new folder called the search and inside let's create page.vsx let's go ahead and create a search page and let's create a div search page like this and now when you type something in your navbar here you should not be getting any errors so just confirm you're not getting any errors I had one error but I cannot see it again so I guess it was just some cache great, it should basically just say a search page And what I can do from here now is I can create an interface search page props and since this is a server component we can access search params the same way we would access params in dynamic URL. So instead of writing params, and then, you know, we usually write username, what we can do is we can ask for search params, which are going to look for the query inside of our URL. And we know that we have a term, which can be optional. So write it like this.
And then inside, let's go ahead and assign search page props here and let's extract the search params and what I want to write is if we don't have search params term Go ahead and simply redirect from Next Navigation to the root page. So make sure you import this. And now what should happen is that if you have something in your search and in your URL so search should have a term property, it should stay on the search page. But if you just go to slash search it should redirect you back to the homepage. Exactly.
So that's the behavior that I want for this page. And now let's give this a class name of hFull padding of 8, maxWidth of screen2.xl and mxOutfill. Like that. And then inside I'm going to render the results but they're going to be different results so we're not going to reuse the ones from before because we want them in a different layout for search here. So let's go inside and create underscore components and let's create a new file results.tsx like that.
Let's create an interface results props to accept the term whoops which is an optional string like this and let's go ahead and export const results let's return native results and let's make them accept the results props term like that and inside of here let's render an h2 element results or term and then let's for term and then let's use quote like this, sorry, quote like that, term and then another quote like this. And let's give this h2 a class name of TextLargeFontSemiBold and margin bottom of 4. Like that. And while we are here let's also do ExportConstResultsSkeleton and for now you can just return an empty div. Now go back inside of the search page right here and go ahead and import the results from slash components results so don't accidentally import it from our home page because that's the different one let's wrap this around suspense and you can import suspense from react as I did here and let's pass in the fallback to be results skeleton from .slash component so from the same import that we have here and let's pass in the term to be our search params term like this, great!
And now if I go ahead and search test here there we go it says results for term test if I try something else it says exactly that if I clear everything and click again nothing happens but if I go to slash search directly I'm redirected back to the home page. Great! So just make sure that this is working that you have the term. In case you're not getting the term in your URL go ahead and visit my github or you know go back in the video if that's easier for you. It should be inside of Browse Components navbar search.
So in here we have the logic which adds that term from the input to our URL. So Go and find this component and just confirm that it looks the same as mine in case you're having any issues. Great, now let's go back inside the results page here and let's turn this into an asynchronous function because in here we're going to call our search page but we first need to create our search service. So let's go inside the lib folder and let's create search service.ps and let's import the database and let's import getSelf from the ALF service and I'm gonna go ahead and replace those with add slash lib like this and now let's export const getSearch to be an asynchronous function which accepts the term which is going to be an optional string. Now let's go ahead and do the trick for logged in and logged out users.
So wait, get self, user ID is self.id, catch, user ID is null, like that. Streams is going to be an empty array. And now let's go ahead and first do the easy one. So if we have the user ID, I'm going to write a comment to do less than the easy one first so in here do streams to be await database stream find many like that where and now we're gonna go ahead and use the search either by username or by name. So in our Prisma schema we added this preview features for full text search and full text index and I believe in my stream model we added at at full text index here for the name of the stream.
So since we are also going to query by username perhaps I should also add the same thing like here but for user but we're gonna see I'm gonna try without that I think it will still work but it's gonna be a slow query so we're gonna see let's go ahead and just continue for now so we're gonna combine both of those so we're gonna use the or operator we're gonna open an array and first we're gonna write the name of the stream contains the term like that. And then in the other object we're gonna write if the user relation username field contains the term username field contains the term as well. Great. And let's also go ahead and add include user true and let's do order by, and let's do an array of items, right? So I'm gonna write isLiveDescending and I'm gonna write updatedAt also descending, like that, and do return streams at the end.
Is it streams? It is. It is streams but so okay now we also have to do it in here and I think that's gonna get rid of this error. So let's go ahead and copy this entire function because it's gonna be very similar so copy this query and paste it instead of this to do here like that there we go that got rid of the error but now what we have to do is we have to extend the where query besides this OR to also not include any user that is blocking us. So let's write user right here not blocking some blocked ID to be our user ID.
So we are not going to show that user that is blocking us anywhere on the homepage, on the search page, nowhere. Great, so I think this should be it for our search service and of course we're going to come back to this just to decide whether we actually need everything from the stream or can we improve those a little bit on security side so we don't include the ingress ID, the stream key and stuff like that. So let's go back inside of the search page, components, results here and now we can go ahead and write const data to be await get search from our search service and pass in the term like this great and now let's go ahead below the h2 and write if data.length is zero. In that case, let's go ahead and write a div, no results found, try searching for something else. And I think we can render a paragraph instead of a div for this one and let's give it a class name of flex sorry, text text, muted foreground and text small like this, so now you should have this text if you search for something that doesn't exist but if you search for Antonio for example which is one of my username there we go, you can see that that doesn't appear because it looks like we do have some results so go ahead and search something that hides this, meaning that you do have some results.
And then let's go ahead and create a div with a class name flex-flex-call and gap-y4. And inside of here, we're going to go over our data.map, get the individual result, and we're going to create our result card, but don't import it because this is also going to be a new result card because it's going to have a different layout so give it data and result.id as the key. Great! And now we have to create our resultCard.tsx inside the search page, of course. Did I misspell this?
ResultCard? Results? Okay, looks okay. And let's go ahead and first create an interface resultCardProps to accept data, which is an entire stream object for now and also a user which is an entire user object and let's export const ResultCard and let's return a div let's assign the props ResultCardProps and let's extract the data And I want to assign that immediately here so you can see the changes that you're doing. So make sure you import result card, refresh your local host, and you should no longer be having any errors.
And now in here let's first wrap the entire thing inside of link from next slash link like this. Let's give it an href of slash data user username and then let's create a div with a class name of width-full-flex-gap-x4 then create another div with a class name relative-h-9-rem and width of 16rem and inside render our thumbnail component from components thumbnail so it's great that we can reuse that this easily and let's go ahead and give it source to be data thumbnail URL. Let's give it fallback to be data user image URL. Let's give it is live to be data is live. Let's give it username to be data user username like that and I think you should already be seeing it and there we go.
You can see how now I have some results page here for the term search Antonio. Let me just go back to the search page here. And now we're going to go ahead and add some information here. So outside of this div right here, which is wrapping the thumbnail, create a new div with a class name, space Y1, then another div with a class name flex items center and gap x2 and then inside go ahead and render a paragraph data user username and let's go ahead and let's style this a bit so I want to give it a class name of font bold text large cursor pointer hover text blue 600 actually 500 like that and let's add verified mark and we can do that from components verified mark like that and then outside of that div add a paragraph for the stream name so data.name and let's give it a class name of TextSmall and TextMutedForeground let's copy and paste this and now we're gonna format our date here. So let's go ahead and import format distance to now from date FNS, which we installed, I believe at some point in our tutorial.
I mean we definitely did I just don't remember when exactly. And in here let's render a format distance to now, new date, data.updatedAt and let's add options add suffix true like this. And there we go. We have our search page you can see how differently it looks from our home page so that's why we created those different components because this way you can see how it has a nice layout and it definitely reminds of a proper search page and when you click on it you should get redirected to that user's stream. Great and now I want to go ahead, oh do we have an error here?
Could be because I stopped it in the middle of loading yeah it looks like it's because okay that's that's not too big of a deal I think you can fix that by I think it's because it's trying to create a token and then it's trying to set state on an unmounted component. So you can add a use effect which unmounts and then breaks the function. I think, I don't know. I don't think it's such a too big of an issue. So I'm gonna focus on other things for now and then maybe we'll come back to that.
What I want to do now is I want to import a skeleton component from components.ui.Skeleton here and I want to create the result card skeleton So let's go ahead and return a div here with a class name of widthfull flexgapx4 and let's go ahead and add a div here with a class name of relative h9rem and width of 16rem and let's render the thumbnail skeleton component so make sure you import that from thumbnail right here and then below that add a div with a class name of space y2 and we're just going to go ahead and render the skeleton component a couple of times so let's give this a class name of h4 and width of 32. Let's copy this three times this is going to have a height of 3 and the width of 24. Last one is gonna have a width of 12 and height of 3. Like that. Now go back inside of the results page and now we're gonna use those results card to actually create a proper results skeleton.
So in here first let's add a normal skeleton to represent our h2 element so import skeleton. Let's go ahead and give this a class name of h8 and width of 290 pixels and a margin bottom of 4 and then create a div here with a class name of flex flex-col and gap-width 4 and let's go ahead and open an array here curly brackets spread an array of 4 elements inside .map go ahead and skip the first one get the index and render result card skeleton from .slash result card So just make sure you do both of the imports for the result card from your closest import because we have same named components from our homepage. So just make sure you don't use those because they have a different layout, right? They're not for search. And let's give it key of index like this.
Did I do this correctly? I don't think I did this correctly. No, I did not. Let me try this again. Okay, so opens curly brackets, go ahead and write an array of four elements dot map, skip the first one, get the index and then inside render the result card skeleton and again I have a feeling yeah I make a mistake sorry right here this is where I'm supposed to put dot map sorry okay get the index and let's go ahead and third try is it correct now?
Honestly I don't know okay so I was confused because I'm not I wasn't getting this error for the missing key So I assumed I did something wrong with the map Iteration and I did it wrong in the first time because I started dot map chain here when it should have started here So what I did was just refreshed my Visual Studio code and now let's do key I like this. Now it should be correct. Okay. And I believe that if I go inside of my search page.csx, I already have this suspense and there isn't much I should do here. So now when I refresh, there we go, we have a nice loading skeleton here as well.
Great, you finished the entire thing. All that's left is the community tab here. And of course, we're going to go back to the search results just a bit later to see if we have some security issues by passing the entire stream. Great great job.