In this chapter, we're going to focus on building the home feed, which is this screenshot right here. Luckily for us, we've developed all components needed, so not much will have to be done on the UI side. But what we will have to do is create videos procedures. And since the nature of home feed, subscriptions feed, and the trending feed are all very similar, we're going to try and wrap them all up in this one chapter. Let's start by building the videos procedure.
So I'm gonna go ahead and go inside of my source, inside of my modules, and I'm specifically gonna go inside of the videos module right here in the procedures. And now I'm going to create the get many procedure. Now we can save some time and go inside of our search procedures and we can copy the base procedure from here. So I'm gonna copy everything including the pagination like this and let's paste that here. Now I'm just gonna go ahead and modify this indentation and then we're going to slightly remove some things we don't need.
First of all, this will not be needed. We will not have a query at all, in fact. So you can remove the query from the input here and you can also remove the query from the input object. Next, let's go ahead and import OR, larger than and descending. All of those things from Drizzle ORM.
Now that we have that instead of getMany, let's go ahead and slightly modify it so that it returns us exactly what we need here. So I think that just by removing the I like method, we're pretty much done with our home feed here. But I do want to add something that was actually missing in the home feed, which is the visibility. We only want to load the public videos in our home feed. Which reminds me that on my search procedure, in the search router, I should do the same thing.
So let's go ahead and add that here as well. Videos, visibility, public. There we go. And now we have the get many procedure for our home feed. So we are loading the user or the author of that video.
We are loading the view count, the like count, the dislike count. We are joining the user and we have the proper pagination here. And we also have the optional querying by category because we will have the category on the home page. And this pagination should work because it's exactly the same as the ones we have already. Perfect!
So no need to add anything to our TRPC router here because video routers already exist. Instead, let's go ahead and go inside of our app folder home and let's go directly inside of the page.tsx and this time besides the categories we are also going to prefetch infinite videos here. And let's go ahead and pass in the category ID here. And let's pass in the limit to be the default limit from the constants. There we go.
So now we have prefetched our videos and now what we have to do is we have to go inside of the home view and besides having our categories section let's also add home videos section. You can also call it a videos section if you prefer so. And let's pass in the category ID here so we can do the proper use suspense query inside. I'm gonna go inside of the sections and create Home Videos section.dsx. Let's create, well, I will just copy the interface.
There we go. An interface Home Videos section props and let's export const Home Videos section. And inside of here, let's go ahead and return suspense. From React error boundary, from React error boundary, and Home Videos section suspense. Now let's go ahead and get the props home video section props and let's go ahead and spread the props here let's add a fallback here error And let's go ahead and create a constant Home Videos section skeleton.
For now to simply say loading. Then we can use this in place of a fallback right here. There we go. Now let's create the home videos section suspense, which will have the same props, but this time we can destructure it. And inside of here, we are going to fetch our videos and have a query from TRPC from the client side.
So make sure you import the one from the client side. Trpc videos get many use suspense infinite query and pass in in the first argument here the category ID the limit of default limit from the constants, and then you have to pass the get next page param. There we go. Make sure you have imported this from the constants. And now we have the videos and the query.
So you can go ahead and return JSON stringify videos. Now go inside of the home view and you should be able to import home videos section And if you go ahead and refresh your home feed here, looks like we have an error. Cannot read properties of undefined reading get many. Let's see what's going on here. So we have video router here and we have get many.
There should be no error here. Let's see. TRPC videos get many. Let me confirm inside of home video section I probably didn't mark this. I definitely didn't mark this as use client.
And that seemed to be a problem. Definitely not as descriptive as I would want it to be, but at least it solved the issue. So make sure that you have marked your home video section with use client. And now we have the videos here. Perfect.
So what we ought to do now is create a grid and we can reuse our cards. So this is what we are going to do. We are going to go over our videos.pages, do a flat map, get the individual page, return all the items from each page and then simply map over all of those here. Get the individual video and simply return a video grid card, passing the key to be video ID and passing the data to be a video. Make sure you have imported this from the module.
And you should now start seeing all videos available. Obviously, we now have to put them in a grid. So let's go ahead and do the following. Let's add a div here and let's start adding the proper class names. Let's give a gap 4 between each of our elements.
Let's give a gap Y of 10 in case we collapse to mobile. Let's activate the grid and now we're gonna start from the lowest breakpoint which is mobile. So grid columns 1. On small devices, grid columns 2. On large devices, we're gonna do grid columns 3.
You should already be seeing a change here. On extra large devices, we're going to repeat grid columns 3. On 2XL, we're going to repeat grid columns 4. And as far as I know, this is the maximum that we can go using built-in Tailwind classes. But you can see that there is this area here where the videos simply take too much space.
So what we are going to do is we're going to create our own media breakpoints here. So open square brackets, add media and add a minimum width of 1920 pixels like this. And make sure that you add no spaces like this because then that breaks the thing. So it has to be like this. And make that a grid calls five, like this.
And then you can copy this and add one more which will be the largest break point we are going to support, which is going to be 2200 pixels. And that is going to add six columns. There we go. So now this looks much better and it has the limit to how wide it can go and it also has a mobile collapse right here and there we go that's pretty much it that is our home page and now what we have to do is we have to add our infinite scroll here from components infinite scroll and you simply have to pass all the things necessary from the query here. So pass in the has next page, is fetching next page and fetch next page and you should now see the text you have reached the end of the list.
Excellent! So that's it. That is our home page. So now what I want to do is I want to copy this entire thing here and I want to go inside of my skeleton and I want to add the skeleton here. And instead of doing this whole thing, what we are going to do is we are going to create an array from length 18, and we're going to map, skipping the first element, going to the index here, passing the key to be the index, and we are going to use video card skeleton, meaning no need for the data prop.
There we go. That is our skeleton section, so make sure you have imported the skeleton. And now when you do a hard refresh, you should see this as well. And your Home feed should also be reactive to any of these changes right here. So that means that we should probably give the suspense here a key of props category ID.
So it re-triggers every time that changes. So if you click here now, there we go. It should re-trigger the suspense every time we change the category. That is our homepage. So You can see that my videos will load on comedy, but they won't load on this one.
Excellent. So I just want to double check, because this text seems a bit large, but I think that's because I'm zoomed in a lot. I think this is actually just okay. There we go. And this looks great.
Now that we have this, let's go ahead and let's build the subscriptions one. Actually, let's build the trending one first because it's easier. The subscriptions one will require some setup with two users and other users' video. So in case you don't have that, let's finish the easy one first, which is trending. So in order to finish that one, we're going to have to go inside of app, home, and what I'm gonna do is I'm gonna create a feed sub route, and then I'm just gonna go ahead and create trending inside.
And you can copy the page DSX because it's gonna be so similar. So simply pass that inside of trending right here. And now the trending itself will be a little bit different, starting with the fact that it will not have any search params. So no need for any props at all, no need to await anything here, and no need to prefetch the categories either. So let's now go ahead inside of our videos procedures and we are now going to build the getMany trending.
So let's copy the entire getMany here. I did explore like adding just a simple prop here, maybe is trending, but you can decide for yourself. So I'm gonna copy the entire thing and you will see the changes needed. Let's call this get trending base procedure here. And we are gonna remove the category id.
We will just leave the cursor and the limit. We will remove the category id from the input destructuring here as well. And what we have to do now is we have to add const view count subquery. So let's add that count here using the video views and using equals video views video for this video ID basically like this So we are building this subquery because we are going to need it because we will be ordering the videos by their view count. So now I'm going to be using this subquery in place of this because it's exactly the same actually but when it's in a constant it will allow us to do some ordering by that subquery.
Let's start by removing this unused category ID here. And now our cursor here will work a little bit differently. We're primarily going to focus here, instead of videos updated at, we will focus on view count subquery. And our cursor will not have updated at, it will have view count. Now we have to modify our cursor back up there and change the cursor to include view count and view count will be a number.
Now we should have no errors in this place for looking at the view count. But what we do have to change is this. In here, we are now ordering by view count subquery, like this. And I just have to double check to see if everything's still okay. So this is view count subquery, view count, view count.
I think this should be it, but we do have to modify the next cursor to use the view count and use the last item dot view count right here. And I'm pretty sure that this should be enough for us to basically order the videos by highest count. So now let's go here and let's use Get Many Trending. And why is that? Oh, Get Many Trending.
And there will be no category ID for this one. And instead of having a home view, we will have a trending view. So let's just add trending view. And you can remove the home view import. We can close the app folder and let's go inside of modules.
And well, we can build this instead of home. So let's go instead of views. You can copy the home view, paste it, and you can go ahead and rename this to trending view. Go inside of the new trending view and rename this to trending view and it's going to be a little bit simpler due to no props. You can remove the categories section entirely and inside of here what you're going to do is the following you will add a div here with an h1 element trending and the class name text to Excel and font bold and below that a paragraph let me just fix this first.
A paragraph will say most popular videos videos at the moment. And let's give this a class name of TextExtraSmall and TextMutedForeground. There we go. Now I want to slightly modify this, I think. Let's see.
Or maybe not. I think it's actually okay. And now what we have to do is we have to build the trending videos section, which lucky for us is gonna be almost identical to the home videos section. So let's paste it here and rename this to trending videos section. Go inside of trending videos section, remove the prop because we're not going to use it for anything.
This will be trending videos section. The key will not exist in this case. Let's replace all instances of home here with trending. No need to spread any props, they won't even exist. So this is the trending videos skeleton, it can stay the same.
And this will be trending videos section suspense. And remove the props from here and use get many trending here and remove the category ID. And everything else can stay the same. If you want to, since you can obviously see that these two are extremely similar components, you can extract it into one unified example if that's something you like. And instead of rendering home videos section in the trending view we will now render trending videos section and we're not going to pass any props.
So now we should have the trending page. Let me just see the error which I have, which is the missing trending view. And now we have to go inside of our home sidebar, I believe, instead of Home UI Components, Home Sidebar. In the main section, I wanna confirm that my URL is slash feed slash trending, which is exactly the same as my slash feed slash trending page. So when I click in trending, I should see this two videos still, But one of this has, they both have the same number of views.
So right now they are ordered by their ID. So this video for me happens to be the second one. So if I were to change the videos titled, this should be a suggestion views to a larger number of views it should be ordered first one after a refresh so make sure you have your Drizzle Kit studio running and let me find that this should be a suggestion and I'm going to oh yes I can't just increase the video views. Yes, because it's one user per video. Oh, but I know what I can do.
I can delete another video. Either go ahead and log in with another user and watch a video so you can increase the views or simply delete the other video views. So I'm gonna go ahead and do the opposite. Let me close this one. So this is the video untitled.
I'm going to delete that video's view. So now when I refresh, this should be a suggestion is listed above this one because it has one view and this one has zero views. Excellent, our trending now works. Perfect, So now you need a setup for implementing the subscriptions. In order to do that, go ahead in another account and upload a video from another user.
I'm going to pause and do that. So let's meet at the homepage. There we go. In case a video is not showing for you, remember you have to set your video to public, right? If you left it at private, it's not gonna be on the homepage.
Great, so now I'm at the homepage here and I'm not sure if I'm subscribed to this user or not. So let me just see, I'm not subscribed. So this is actually a good example. Let's build a subscriptions page while I'm not subscribed to this user. So what should happen is that I should not see this user's video on the subscriptions feed.
So let's go ahead back in our procedures inside of our modules. Well, I can just search for videos procedures. There we go. And let's copy the GetMany trending. Actually, I want to copy GetMany.
I think this one might be more similar because of this cursor which we heavily modified. That's why I don't want to copy the GetMany trending. I want to copy GetMany and I will call this one GetManySubscribed like this. And now we have to modify it so that it only loads the videos from the users we are subscribed to. Now one big thing about this is that this will only be available for subscribed for logged in users, right?
So instead of a base procedure this will be a protected procedure. So make sure you import that in case it wasn't. And we're not going to have any category ID so you can remove that. You can remove the category ID from the input, but you can add context here. And you can extract ID, user ID from context user.
This is our database user ID. And now what you can do is you can create a common table expression called viewer subscriptions. Database with viewer subscriptions as database select user ID subscriptions, where they are a creator ID, from subscriptions. So import the subscriptions, or you already have subscriptions, let's see. From database schema, we already have subscriptions right here, great.
Where equals subscriptions, Viewer ID is equal to user ID. So basically we are preparing a common expression table, which is like a temporary table that we are going to left join on. And what we're attempting to do is we are attempting to load every single subscription model in our database, where the currently logged in user is a viewer. And the only thing we're interested in this dataset is the creator ID. So we are selecting that creator ID here and then this is what we're going to do using that.
We're first going to have to join this using not a left join, we're going to need an inner join because we are exclusively loading videos for the user that we are subscribed to. So if we were to use left join, we would get both those users that we are, both the videos from the users we are subscribed to and the ones we are not. But in this case, we wanna use inner join so that we only and exclusively load videos from the user we are subscribed to. So let's add the Viewer Subscriptions Common Expression table, and let's query it even further down by targeting the user ID. We assigned it to be the creator ID, and simply combining it in the query here of users ID, because we have an inner join on the users as well.
So we will be able to query by each individual user. Go ahead and remove the category ID here, and I'm pretty confident that the cursor can stay exactly the same. I don't think we do any weird querying here. I'm comparing with my source code just in case because this this part always confuses me but I'm pretty confident it works as expected. There we go.
Okay, I think all of this is completely fine. So what we're gonna do now is the very easy part, go inside of feed and just copy the trending and rename this to subscriptions, like this. Go inside of page. And again, no need for any props, just get many subscribed like this. Or maybe we could call this Subscribed rather than subscriptions.
Maybe that's a better name. I don't know. Subscribed, subscriptions, however you want. We will double check with our sidebar to make sure we are redirecting to the right place. But since the procedure is called subscribed, maybe I should call this subscribed as well.
I don't know. So only the feed of our subscribed users. And instead of using a trending view, we're gonna go inside of our modules, inside of our home views, copy the trending view, paste it and rename it to subscribed videos section. And now I'm gonna replace all instances of trending here. So one, two, three, four, five and six to subscribed.
So here's what I changed. Subscribed video section, subscribed video section skeleton, so subscribed video section suspense, the skeleton component itself, the video section suspense itself, and the TRPC get many subscribed. There we go. So what's important is that instead of the subscribed page, you prefetch infinitely the same thing that you suspense here. In case you are doing this incorrectly, don't worry, you will see an error.
And I think that we have to go inside of our Home sidebar, inside of the main section. Yes, I called this slash subscriptions. So double check. If this is slash feed subscriptions, instead of your app home feed, it should be subscriptions here as well. So either change this to subscribe or change this to subscriptions, right?
It needs to match. Otherwise it will not be able to load. Subscribed. I think I did it correctly. So if I click in subscriptions now here, it still loads trending.
And I think I know why it loads trending. Let me go inside of my subscribed page. I load the trending view. So let me remove the trending view and import subscribe view. There we go.
I now expect no videos. Okay, still not good. Feed subscribed. Subscribed view. Oh, it uses the trending video section.
Okay, so this should now be subscribed videos section and remove trending videos section. Third try. Oh, and still an error. Okay. Yes, yes, yes.
I think I know exactly what this is. We forgot to include our common table expression properly. Let's go inside of our videos, server procedure, get many subscribed. When we create a comment table expression, we always have to add it here. Fourth try, and there we go.
No error, but also nothing here. So let's go ahead now and go inside of another user's video and let's subscribe to their video. And now let's go inside of subscriptions here, do a hard refresh and you should be able to see this user's video because you are subscribed to that user. Excellent. So we've wrapped this up.
We implemented home, subscriptions, and trending. So if you now want to, you can find the subscription button, more specifically maybe use subscription. Yes subscriptions modules subscriptions hooks use subscription and you could do utils videos get many subscribed invalidate? So the new, so immediately without refresh, it should be available. So in my subscriptions, it's now available, but if I unsubscribe from this user and go back to subscriptions, you can see how it re-invalidated, so it's removed.
If I subscribe to the user again I should see their videos now. Oh because this is for unsubscribe I have to do the same thing for subscribe. There we go. So now let's try again. Unsubscribe, subscribe.
There we go. Without refresh, it's working perfectly. Amazing. So that is exactly what we wanted. If you can, If you want to, you can change maybe the text subscriptions here to Home Sidebar, Main Section.
Maybe to Subscribe if you want to be consistent. Or if you like subscriptions, change the subscriptions. You know, just be consistent with your routes. Great, so we have these three implemented. So what's left to build is history, liked videos, playlist functionality, and the last thing, I think we will leave this for last, is going to be the individual user page.
Other than that, I think you've pretty much completed the hardest parts of the video, the most complicated parts of the video, And I think that even if I left you alone to build the rest, you could do it, but don't worry. We will build the rest together. Let's go ahead and mark these things as done. We've created the videos procedures, homepage, trending page, and the subscriptions page. Amazing, amazing job.