So let's go ahead and let's create our home page. So we already have some things prepared here. Inside of our app folder, inside of a browse, we have a route group home and inside page.csx. That is this page that you're seeing right here. So what I'm going to do now is I want to add a limit to how wide this page can expand to.
So we can do that by first giving it an HFull class, a padding of 8 and then a max width of screen to Excel and then MX auto and what this is going to do is limit how far this screen can expand and the content inside. So focus on the text home page here and take a look at how I expand my screen it goes more to the left right But what that class name does is that when we have extremely large wide screens you can see that at some point it stops going to the left. So I keep expanding but the home page is no longer close to the sidebar. So that's what I want to achieve because otherwise our grid and our columns is just going to look too big and it's impossible to keep them looking good at such a wide screen. So that's why we're going to limit to how far, how wide we're going to support our results screen.
Great! So what I want to create now is my feed service. So we can technically also hold this inside of the stream service but I don't know, I want to keep it inside of feed service so you can go ahead and play with it later and maybe expand it to some more functionality. So I'm going to create feed service and I'm going to export const getStreams which is going to be an asynchronous function like this. And now let's go ahead and let's import the database from .db and let's import getself from the out service and I'm just going to replace this to to use slash lib like this so now let's go ahead and see if we are logged in so we're going to attempt to fill the user ID using self so await get self and then I'm going to assign the user ID to be self.id but in the catch I'm just going to set the user ID to be null.
Like this. And so now we handle the case if there is a logged out person looking at this or if it is a logged in person. So now let's define our streams to be an empty array. And now I'm gonna write if we have user ID I'm gonna write a comment load by user ID else we're gonna do a very simple function here so we're gonna write streams to be equal to await database dot stream find many let's include the user of the stream and let's order by isLive to be descending like this and I believe we can actually combine orders so let's go ahead and do this. Let's do an array and then first let's do isLiveDescending and then let's add another one by updatedAtDescending.
So first we're going to display live streams and then we're going to go ahead and order by which one was the latest updated. Great and I'm gonna come back to this later because I feel like we don't need everything from the stream right especially we don't need the stream key, the ingress ID, all of those confidential information. And we can also fix that I believe in the user service we have get user by username and in here we include the entire stream. So I'm gonna come back to that because I feel like it could be a security issue. But for now let's continue doing it like this and we're going to come back to it.
Now we have to handle the case if we are logged in. So if we are logged in we only want to load the streams from users which have not blocked us. So we have to ensure that. So let's do streams to be a weight DB stream find many where user is not blocking some blocked ID user ID so we ensure that this user which has this stream that we are trying to query does not have our user ID inside of a matching blocking relationship like that So now let's go outside of where and let's include user of that stream and let's do the same order by so you can copy it from here and you can paste it here like this. Great!
So now we have our get streams function now we can go back inside of the page here and in here I want to render my results which I don't have yet so instead of the home page create a new folder underscore components and create a new file results.tsx like this. So this is going to be a server component. So let's go ahead and do for now just results and return a div results like this and let's go ahead and add them here and there we go we should no longer be having an error instead we should have a text which says results and what I want to prepare is the skeleton so results skeleton is very simply for now just gonna be an empty div and the reason I want to create it now is I don't forget it later so let's go ahead and wrap this inside of suspense. Suspense from react and go ahead and give it a fallback to use the results skeleton like this and make sure you import the results skeleton. Great!
And now let's go inside of results and let's use that feed service. So let's turn this into an asynchronous function and then let's write const data to be a way to get streams from our feed service. And then inside of here, let's go inside of this div and let's create an h2 element, streams with think you and let's add a little apostrophe here streams with think you will like. Like that. And let's give this h2 a class name of text large font semi bold and margin bottom of four like that and let's see if I'm getting any errors I'm not great so we have a text which says streams we think you will like.
And now here I'm going to write if data.length is zero. In that case, let's go ahead and write a div, no streams found. And give this a class name of textMutedForeground and textSmall. And I believe that I forgot to return something from get streams so let's go back inside of feed service and yes I completely forgot to return this streams so after we do this if clause is just return streams at the end like that so it's definitely going to be an array one way or another great so we have get streams here so now this should be working let's refresh here and you should not be seeing this even if you don't have an active stream because in here we simply show all streams both offline and online and now in here I want to go ahead and I want to create a class name Grid, GridCalls1, MD, GridCalls2, LG, GridCalls3, Excel, GridCalls4, 2xl, GridCalls5, and gap of 4. And let's go ahead and iterate over our data.map.
Let's get the individual stream. Let's create a div here. Let's give it a key of stream.id and let's render stream.name. And we should have two items here. So one is the stream that I renamed to 123 and the other one is code with Antonio's stream which I have not modified.
Great! So now we're gonna replace this div here with a component called result card. So let me go ahead and replace this with result card like this and let's go ahead and let's give it a key of result.id and data of let's call this result. So we have result.id and data is going to be the entire result. Now inside of components create a new file result card.vsx and let's go ahead and do an interface result card props to accept data which is a type of stream from Prisma Client and it also includes the user like this so singular and let's export const result card and let's assign the props result card props and return a div result card like this now go back to the results and import result card from .slash result card like this Let's refresh this and there we go you should have two instances if you have two users you should have two instances of result card.
So now I want to wrap this entire thing inside of a link component so import that from next slash link like this and let's go ahead and destructure the data and in here let's go ahead and pass in the href to go to slash data user username like this great and now inside of here I want to create a class name h full width full and space y space y or and now inside we're gonna put our thumbnail but keep in mind that not every stream will have a thumbnail so this component that they're gonna create now is gonna be able to use the user's avatar and use that as a fallback in case it doesn't have a thumbnail and we're also going to create a cool hover effect so that it looks like it's kind of floating when we hover on it and it has the blue background which is the primary color of our application. So it's going to be a really cool effect. So let's go ahead and replace this and just add a thumbnail component which we don't have yet but we're going to create. And let's go ahead and pass in the source to be data.thumbnail URL let's create a fallback to be data.user.image URL image URL let's go ahead and pass in isLive to be data.isLive and let's pass in the username to be data user username like that.
And now we're going to reuse this thumbnail component in the search page so I'm going to create it inside of my global components folder so thumbnail.csx like this and in here I'm going to create an interface thumbnail props to accept the source, which is a string or null, fallback, which is a string, isLive, which is a Boolean, and username, which is a string. And let's go ahead and export cost thumbnail. And let's return a div thumbnail like this. And let's go ahead and let's assign the props, thumbnail props and extract the source, fallback, isLive and username go back inside of the result card and import this from components, thumbnail, like that and you should no longer be having any errors instead you should see the text thumbnail and you can already click on this and it should redirect you to the stream of that user, right? But for now make sure you are on the home page, this is what we are currently developing.
Alright, so inside of the thumbnail we're going to handle the case if we don't have a source and if we have a source. So this is what I want to do first. First I want to create kind of my default content here so let's give this a class name of group aspect video relative rounded medium and cursor pointer like that. Now inside I'm going to create another div which is going to have a class name of RoundedMD Absolute Inset0 BGBlue600 and let me just write something inside. Oh, actually I don't have to.
Great, so you can already see it. Perfect. So BG Blue 600 by default is gonna have an opacity 0, but when we do group hover it's gonna have opacity 100. So now when you hover over you should see an opacity it should be invisible but move your cursor over the screen and you should see the thumbnail great so just make sure that you have group hover and that you added the group class to the parent element like that and let's give a transition to the opacity so it looks just a little bit smoother. Alright and now let's create flex items center and justify center like this.
Great And now inside we're gonna go ahead and render our content. So let's define content outside of the return here. Let content. And inside of this div we're gonna render that content. And it's gonna be dynamic depending on if we have the thumbnail or not.
So let's go ahead and check if we don't have a source in that case content is going to be the following. Let's create a class name with BG background like this. Let's give it flex flex call items center justify center gap y for height full width full transition transform group dash hover translate x to group dash hover minus translate minus y minus 2 and roundedMD. And I think you can already take a look at it and see how we have a nice little effect now. You can see how it almost floats when we hover on something.
Great! And inside of here we're gonna go ahead and simply render the user avatar from .slash user avatar. I'm gonna change this to use the components like this. So import that. And in here we have to pass a couple of props so size is gonna be large.
We're gonna pass in the show badge prop, the username which is gonna be the username, image URL which is gonna be the fallback and let's also give it is alive or is alive like this so since I don't have thumbnail in at least one of my items there we go You can see how this looks now. Great. And let's go ahead now and let's create an alternative which is going to be even easier. So the alternative else. Let's go ahead and create a div here.
Actually, no. What is going to happen is it's going to assign a content, right? And it's simply going to render image from next slash image. So make sure you import image from next image here. And it's going to render that original source.
So let's give it a source of source, a fill property, an alt of thumbnail and let's give it a class name of object cover, transition transform, group hover, translate x to, group hover, minus translate minus y minus 2 and rounded md like this. Great! And now I believe that we can also create a thumbnail skeleton but let's just see. So when I refresh here I think I might have made some mistake here because I don't think this should be completely hidden by default let me just go ahead and confirm So yes I made a mistake here so you can see that by default our opacity is zero and then only when we hover something appears. So that's how it's supposed to work but I made a small mistake I put content inside of that div but that div is supposed to be a self-closing tag.
So go ahead and move content out of this div and you can just remove the end tag and make it a self-closing tag like this and now it should look better. There we go. So now we have our grid here as you can see we have a nice little effect and if we don't have a thumbnail you can see that it has this little hover right. So now I'm going to go inside of my currently logged in user. So I didn't even have to be in the creator dashboard.
From here, I can add a thumbnail. So I'm gonna go ahead and add my screenshot here that I took. Let's go ahead and upload this image just to confirm that our thumbnail works as well inside of that grid. So let's wait a second for this to upload. There we go the thumbnail is here and when I go here there we go I can see the thumbnail right here.
So now I want to go ahead and create the skeleton for the thumbnail so that we can easily create a larger skeleton later on. So go inside the thumbnail here and let's prepare our skeleton. So import skeleton from .slash UISkeleton but I'm going to change it to slash components UISkeleton and let's go ahead and do export const thumbnail skeleton to return a div with a class name group aspect video relative rounded Excel and cursor pointer and inside we are going to render our skeleton component with class name hfull and width full. Great! Now let's go back inside of the result card and we can continue developing.
So we lastly added this thumbnail which we were developing and now I want to go ahead and render if data is live in that case let's go ahead and render our div like this which is going to render our live badge from components live badge So in order to see this I recommend that you click start streaming inside of your OBS like this and now there we go I have a huge live badge here so I recommend at least one active live stream you turned on for now and now let's go ahead and position it. So I'm going to give this div a class name of absolute top2 left2 group hover translate x2 group hover minus translate minus y minus 2 and transition transform Like this there we go now It's flush as you can see with this perfect and on here it doesn't exist but don't worry because we're going to have some additional information below. Great! So now that we have this let's go outside of this conditional here and let's render a new div with a class name flex-gapx3 and let's add the user avatar component from components user avatar so make sure you import that let's go ahead and give it a username of data user username let's give it an image URL of data user image URL and is live property of data is live like this and below that I'm gonna add a div with a class name flex flex call text small and overflow hidden like this and let's create a paragraph which renders data.name which is the name of the stream and let's give it a class name truncate truncate font semi-bold hover text blue 500 like that and another paragraph which renders data user username and give this a class name of text muted foreground and there we go we have our home page and you can see how even if...
Let's go ahead and give this a thumbnail just to see how it looks. Actually I have this other browser. Great. So I'm just gonna go ahead and add a thumbnail to my stream here just to see how that looks while a stream is active I don't think anything is different but I just want to show you that you can still tell by that indicator on the user avatar. Great so now my stream has an image here So let me just refresh here and there we go.
You can still see that it's active because of this little thumbnail that we have here. Great. So we finished with the result card and now I want to create the result card skeleton. So let's export result card skeleton here. And I wanna go ahead and prepare the skeleton components from components UI skeleton.
And let's go ahead and return a div with a class name of hFull, widthFull and space y4. Let's render the thumbnail skeleton which we created. So import that from components thumbnail. Below that let's go ahead and add a class name flexGapX3 and render the user avatar skeleton. So make sure you import user avatar skeleton and below that we're gonna create a new div with a class name flex-flex-col-text-small actually we just need gap-y1 here And let's add a skeleton here and let's multiply it.
Give the first one a class name of h4 and a width of 32 and the lower one class name of h3 and width of 24. Like this. Great! And now we can go back inside of our results page and we can create a proper results skeleton. So inside of this div, add a normal skeleton first.
So import that from components UI skeleton. Let me just join this two together. And let's give this one a class name of h8 and a width of 290 pixels and margin bottom of 4. So this skeleton is representing this h2 text. And now we're gonna go ahead and create a div here.
And what we can do is simply copy the entire class name from our original div. So paste the class name like this. And then inside we're gonna mock an array. So spread an array of four items dot map get skip the first one get the index and render result card skeleton make sure you import this from result card and simply assign it a key to be the index like this. Great!
So now we should have a proper loading state as you just saw in a second here. Perfect! So we finished our homepage. Great! And now I want to go ahead and see how much of the stream I actually need, right?
Because we're passing the entire data here. But let's see what do we actually need from the stream. So we need the thumbnail URL, we need isLive, and we also need the name of the stream I believe let me just find where we use it there we go name so let's go to the feed service here and let's modify this a little bit. So I'm logged in, so first I'm gonna check this one. Outside of where, I'm gonna add select and I'm gonna select thumbnail URL.
I'm gonna select the name and I'm gonna select is live. Like that. Did I do this correctly? Looks like I did something wrong. So let's see where did I do it wrong.
Where do I have to put it? Select here. Is live true? Maybe it's because of the type of this stream. If I give this any, no.
Maybe it's because of include. So if I go select from here, yeah. So let's go ahead and select user to be true, is live to be true, name to be true, and what did we say, thumbnail URL to be true? I think that's all we need. And I'm gonna copy this and I'm gonna assign that in here as well.
So make sure that both in your user ID you only select this couple of things and we don't need the include because we're gonna do that using select now and now my results oh we also need id yes so let's go ahead and select id to be true up here and up here Okay and now yeah our result differs now so let's go inside of the result card and let's modify this to be an object which has a user which is a type of user is live which is a boolean we have name which is a string and we have thumbnail url which is string or null. And there we go that resolved our type error here. Perfect and now everything should still be working let me just refresh to confirm there we go I have the thumbnail URL I have the name of the stream and I have my user name and I am not spilling any additional information great all right and that wraps up our home page and we fixed that little security issue what we had. What I'm gonna do next is I'm gonna take a look at my user service and see if we can improve that in the stream here and then we're gonna wrap it all up with the results page and the community tab where we can unblock users.