In this chapter, our goal is to implement playlists. Specifically, we are going to focus on history and liked videos playlists. This is because those are not custom playlists. They are a simple aggregation of the data we already have using video views and video reactions. We are going to leave custom playlists for their own chapter as they will be a little bit more complicated.
So our goal for this chapter is quite simple. Create playlist procedures and create history and like the video pages, which are going to be almost identical. Let's go ahead and develop the playlists module. So I'm gonna go inside of modules and I will create a new folder called playlists. And inside of here, I'm going to add a server.
And what we can do is we can copy the videos procedures here and paste them inside of playlists. Let's rename the videos router to playlists router and let's go ahead and remove everything besides the normal get many. So I'm just going to leave get many, the base procedure and let me remove get one entirely. We're not going to need that and we're not going to need anything else here. I can go all the way down and remove everything besides the normal get many base procedure.
And we are now going to go ahead and modify this. So we are going to focus primarily by creating the history playlist first. This is going to be a protected procedure, so make sure you're using protected procedure. It's not going to have any category query, and the cursor can immediately be modified because we will be sorting by when we viewed something. So let's use viewedAt as our cursor field here.
So viewedAt. The limit can stay the same. Now we can remove the category input and destructure the user ID. Give it an alias of user ID and use context user. Ignore this.
There we go. So what we want to do now is we want to create a common table expression called viewer video views using database with viewer video views as database select And we specifically want to select the video ID, which comes from video views, which we already have in our schema. So video views ID and viewed at, which we are simply going to remap video views and then one of the timestamps. I'm going to use updated at just because I don't know, I think that makes sense. The created one is like the original when it was created, but we might implement some kind of updating mechanism from the admin side later on.
So perhaps we should rely on the updated ads. I don't know, but either one honestly will work as long as you're consistent by using it in multiple places. And now let's go ahead and query to only match for the currently logged in user. So those are the video views that we want to see only for this user. And thanks to returning back the video ID, we will be able to now do with viewer video views.
And now let's go ahead and do an inner join here so an inner join to the viewer video views and equal video videos id matches the viewer viewer video views dot video id So now we query even further by only loading for this video, for this video which is currently iterating over and for this user. Great. So we have that. And I believe that in here we don't have to modify anything. We have the view count here.
But one thing that we should return is the viewed at. And let's remap viewer video views viewed at. There we go. So that's one thing we need to add within our select here. Now we have to modify the cursor.
Let's go ahead and let's remove the category ID query. We're not going to use that. And now instead of using videos updated at, instead of using yes videos updated at, we will be using viewer video views. And instead of updated at in these two places, it's going to be viewed at like this. And now we also have to remap from the cursor here to look at view that.
There we go. And we can leave this one as it was. Great. Now just ensure that you're ordering by descending viewer video views dot viewed at. And the second one can be simply descending by the video ID themselves in case they have the exact same timestamp.
The rest here can stay the same except modifying the cursor to use viewed at here as well. There we go. That is our procedure for loading our history here. So we're going to call this get history. And I will now remove every unneeded import from here.
Let me go ahead and remove all these things. These things as well and these as well. There we go, no more errors. Now let's go inside of our router, so we can now import our Playlists router from modules Playlists server procedures and let's go ahead and add our playlists here. There we go.
Now it's time for us to create the actual playlists. In order to do that let's go inside of source app folder inside of home and let's create a subfolder playlists and inside of here let's create a subfolder history and then a page DSX let's import VR PC from the server and let's go ahead and export default a page here let's make this an asynchronous page and let's prefetch playlists get history prefetch infinite and limit by default limit from the constants. And inside of here go ahead and add hydrate client from trpc server and inside add a history view. Now let's go ahead and implement the history view. Let me just reorder these two.
The history view will be created inside of modules, inside of playlists, and create a UI folder here, and then create the views. Go ahead inside and create history view DSX. And in order to make this a little bit simpler, you can go inside of home, views and pick the trending view. And you can copy the entire thing, go inside of playlists view, history view and paste it here and we're just going to slightly modify it now. Instead of trending view it's going to be history view and instead of trending this will say history and this will simply say videos you have watched like this.
The rest can stay the same the only thing we're going to modify is the maximum width to be screen md. Now we have to implement the History Videos section. In order to do that, we can go ahead and create a new folder called Sections inside of our Playlist module and let's add History Videos section DSX, like this. In order to make this easier we can again visit our home sections and pick the trending videos section once more and simply duplicate the content inside of the new history videos section. There we go.
And now let's go ahead and replace all instances of trending to history. So one, two, three, and four. And I'm not going to modify the query because that's an entirely different thing. So history, videos section. There we go.
All instances now say history, history, history. Now I'm going to modify this to use trpc, playlists, and instead of get many trending, it's just going to be get history. Everything else is going to be exactly the same. And instead of rendering a grid, we will be rendering the following. I will remove the entire class name here and I will simply add flex, flex column, gap 4 and gap y of 10.
Like this. Let's go ahead and inside of the history view, import history videos section and remove the trending videos section. Let's go inside of the page and let's import from modules playlist UI views history view. And now if you go inside of your home sidebar inside of your personal section you should see your route for the playlist. So make sure that this playlist history doesn't have a typo.
So you can safely navigate to your playlists right here, history. So confirm that there are no typos in these routes. And when you refresh the entire page and click on history, you should be redirected to videos you have watched and you can see how that is true. I am looking at videos that I have already watched. We are going to test by removing a video view later on.
Let's go ahead inside of the history videos section. And what we are going to do now is we are going to render this part only if we are in desktop mode. The way we can do that, Actually, grid card will be on mobile mode. So it's going to be flex by default, but on MD it will be hidden. So it should actually be invisible right now.
And then you can copy and paste this one below like this and write the following flex flex call gap for again without gap y10 here and this will instead be video roll card from modules video roll card Let's also add a video roll card skeleton here while we are already doing this. And what we're going to do here is the opposite. It's going to be hidden by default, but in MD it will be flex. So now you should see your playlist like this, but when you hit mobile mode, it's going to collapse into normal video grid call. There we go.
So I believe that is exactly what's in the picture right here. But one thing we are going to change is give this a size of compact. There we go and I think this is exactly what we saw in this picture right here. Perfect! That's our history playlist here and now I'm going to go inside of my Drizzle Studio and let me just refresh it.
So I now have two video views here and I will simply remove one of them. Both of them are from the same user which is me. So I'm going to remove one of these video views and you can see that my history has immediately been cleared. One thing that you probably noticed already is the skeleton, which is incorrect. So let's just do the same thing here.
I'm gonna add a div for my mobile like this. Let me wrap this entire thing in a div. And then I'm just going to go ahead and indent this, copy it, replace this with video roll card skeleton, give this a size of compact, and do the reverse. It's going to be hidden by default and flex on MD. And now my skeletons should match exactly even if I do a refresh.
There we go, all of them are matching. And I believe that somewhere in my code I actually used a different solution for this. I think I used the hook called use is mobile. So I just want to search through my code. Using it in the responsive model is fine.
Using it in the sidebar is fine. The hook itself in use mobile is also okay. But I'm actually using it inside of my results section suspense, which is funny because in my skeleton I simply do CSS. Honestly, I think using CSS is a better solution. So how about I simply do the opposite?
This is for mobile. So it's going to be flexed by default and on MD it's going to be hidden and this is the opposite it's gonna be hidden by default and on MD it's going to be flexed. I think this makes more sense Then I can render both of them, right? I don't have to worry. And I have one hook less that I have to use.
Let's just confirm in my history here. So suggestion. If I search now, I should get redirected to the search page and there we go, I loaded this kind of variant and if I collapse, I loaded this kind of variant and my skeletons seem to be appearing exactly as I would expect them to appear. There we go. So if you want to, you can do that change in modules.
This is, I believe, search UI sections result section. If you want to, you can do that change. So we use one less hook and we are consistent. And you can see that sometimes I choose to use div as the wrapping layer. Sometimes I replace it with a fragment.
I think that both of them behave exactly the same. Oh, one thing that I think it's too much here is this space. I don't think, yeah. Gap four, yes. There is no gap Y10 in the desktop one here.
There we go. That's more like it. Perfect. Now let's do the same thing for the liked videos. So again, let me go ahead and close everything here.
Let's go inside of Playlist Server. And we are going to copy the entire get history. And we are now going to develop the get liked method. So get liked, which is again going to be a protected procedure. Similarly to getHistory, instead of having view that, we will have likedAt, which is going to be a date.
Instead of joining a viewer video views, it will be joining reactions. Let's rename the command table expression to reactions. And we will change this to be liked at, change these two elements to be video reactions. I believe we already have video reactions imported so no need to add anything there. So we are querying from video reactions and we are simply doing video reactions user ID here.
One thing that we are going to modify though is the where. So let's add and here and we're going to use two queries here. We're going to query by the user but also by video reactions type to be liked. This will be the playlist of our liked videos. There we go.
That's it. That's our viewer video reactions. We can now use with viewer video reactions. So now let's go ahead and modify, instead of having viewed at, we're gonna have liked at, which will use viewer video reactions liked at so make sure you've done the appropriate changes here and in our cursor here then what we are going to do is we're going to carefully join that so instead of viewer video reactions of your video views is going to be viewer video reactions. Looking at viewer video reactions ID here as well.
So the exact same method to do in an inner join. And then we change these two to viewer video reactions. We are using liked at in all of these four places. We are leaving this one to be the same and we are using viewer video reactions liked at here. And then down here we are changing both of this viewed at to liked at.
There we go! That's it! That is our liked at playlist here. So now let's go ahead and repeat the process. It's going to be quite simple given that we now have all the necessary components.
So we can copy history and rename this to liked. Go inside of page, simply prefetch get liked. That's it. Now let's go inside of the views here and instead of history view we are now going to have liked view. Go ahead and export liked view.
Let's go back inside of the page and import liked view like this. There we go. Inside of the liked view we now need to create a copy of history videos section and rename them to liked videos section. Oops, I've added an extra letter from the Croatian alphabet. And now instead of here, I will rename all instances of history, history, history, History, and even this one with liked.
There we go. Get liked, liked video section suspense, liked video section skeleton, liked video section, and all instances on liked changed. There we go. And pretty much everything here can stay exactly the same. Let's just go back instead of the liked view so that we can give a proper message here.
So this will say liked, and this will be videos you have liked. There we go. As always, go inside of your personal section and confirm that the URL is correct and matches the app folder here. So I have my playlist, liked page, playlist, liked. When I click on liked videos, I should be redirected to liked videos you have liked.
So if I now go to my home page for example and I pick a random video and I choose to like that video and if I go back here right now it didn't revalidate but if I do a hard refresh first of all looks like we got an error this is interesting we got an error unauthorized which means that we did something incorrect in regards to our prefetching let's go ahead and carefully research I'm going to start by going inside of Playlists, Liked page. Let's see. I'm calling trpc Playlists, Get Liked, Prefetch Infinite. That seems to be okay. Let's go inside of liked here, inside of history video section and that's it.
Instead of rendering a history video section, I'm rendering... Instead of rendering a liked videos section, I was rendering the history ones. There we go. Now I will refresh and no more errors. So while we are here, let's go ahead and find the reactions button or video reactions component.
Inside of our modules videos components you should find video reactions and when you dislike call utils playlists get liked invalidate and do the same there we go so we can remove this to do here and do the same thing when you like and remove the to do here. Perfect. So now I should go ahead and find my third video. For example this one. Like that video.
Go inside of my liked videos here and there we go. This is now shown as a suggestion. This is now shown in my list. Excellent. So one thing that I would like to do is I would like to keep my tabs highlighted while I'm in a specific part of my app.
So let's do that and then wrap up the chapter. Let's go ahead inside of our main section component which is located inside of Modulus Home UI Components, Home sidebar, Main section. And I'm going to add the path name from Use Path Name from Next Navigation. I'm going to move this import right here. There we go, Use path name.
We now have the path name and we are simply going to add is active and confirm the path name with item URL. And let's go ahead and remove the to do here. So now when I click on home, home is highlighted, subscribe, trending, perfect. Now let's go ahead and do the same thing inside of the personal section, which is located in home sidebar personal section. I'm going to go ahead and add path name from use path name.
I believe that it's actually not destructured or is it destructured? Let's see. I already forgot. It's not destructured. Okay.
Make sure you have imported path name. I'm just going to move it here. And let's go ahead and check if path name matches item URL and you can remove this here. There we go. So now when I click on history and like videos, all of them are showing.
Excellent! Amazing, amazing job! Our app is looking absolutely amazing. So now what we have to do next is implement custom playlists which will actually be done through here, right? Through this video menu component and new playlists will actually be created in the all playlists button right here.
So that's going to be an interesting thing to do because these ones were easy to do, right? We already used the existing data but for playlists we will need some new elements inside of our database. Regardless, the app is already looking amazing and it's so fast and it's going to be even faster in production. I love this prefetching system that we have developed so much. Great, let's go ahead and go inside of our...
Where is it? Here. And let's just confirm everything we've built. Playlist procedures, history, and liked video pages. Excellent!
See you in the next chapter.