In this chapter, our goal is to implement the search page. But before we go on to implementing the search page, for which we actually have all the components we need, I just want to wrap up a few things from my previous chapter. And the primary one I want to talk about is this, adding a manual video revalidation. Because a very weird thing happened in my previous chapter. I uploaded a new video and it got stuck in the processing status, right?
It never got to status ready until I deleted the video and uploaded a new one. And even though I went into my Mox assets here and I went into the events, I saw that the video asset ready was fired and it also said that the webhook was successful. But looks like something simply went out of order and my video was forever stuck in the preparing status. So I went researching, and I specifically went into the docs documentation here, mux documentation, and I found that there is a way to list an asset or to retrieve a single asset, right? If we have the asset ID, we are able to retrieve an asset and we can get the playback ID and we can get the status.
So I've got an idea about how we can try and add a revalidate button right here, in case a video gets stuck forever in preparing process. So let's go ahead and try and do that. I'm going to go inside of my modules, inside of my videos here, inside of server procedures. And similarly to our restore thumbnail, let's go ahead and let's add revalidate. So it's going to be a protected procedure, and I will copy this from here because it's going to be exactly the same.
Let me just indent this and it's going to be a mutation. Let me just close it like this. There we go. Now inside of here we can destructure the user ID because only the logged in user should do this and let's do the same thing as we do here. So find the video which you are trying to revalidate.
And in this case if there is no existing video we're simply going to throw new PRPC error which will give us a code of unauthorized, or maybe rather not found. So now, what we are going to do is we're going to check do we have the most basic attribute which will allow us to fetch the asset from Mux. And if I go and think about how we do this, let's find the create method. Let me just find here is the create method. So what we definitely have in every single one of our database records is the mux upload ID.
If we don't have this, there's nothing we can do. No webhook is going to work, neither will we be able to revalidate anything manually because if this part is missing we have no idea what's going on. So what I prefer we do is the following. Instead of fetching an asset, let's find the direct upload. Retrieve a single direct upload info.
So we can do that using the upload ID. And then that will give us the asset ID. And then we can continue searching for the asset. Why don't I say just directly fetch the asset? Well, because asset is made inside of video asset created I believe.
So not video asset ready. I think we do that in video asset created, right? But that is part of the webhook and we are trying to solve issues with the webhook. So I don't feel comfortable relying on an attribute that we are exclusively going to assign through a webhook, when the webhook is exactly what we are building this procedure for. That's why I want to choose the max upload ID.
Because it's the only thing we reliably have from start to finish. So that's my idea. I have not done this in my original source code. So this is my first time trying this. So let's try this.
If there is no existing video mux upload ID, in best case there is nothing we can do with this. In fact we should even remove it. But let's for now just throw a bad request. So my idea for now is to use our mux lib here to get the upload. So let's do const direct upload and let's do await, I suppose, mux, which is already imported here.
So we have that. Great. Let me find Revalidate. There we go. We're going to try and do Mux.
And let me just try and figure out the API. So this would be our direct uploads. So let's go and click on retrieve a single direct upload here. All right, let's go ahead and I hope that their SDK can do this. Let's see, max.get.
I'm going to try and do this some other way. Max retries, patch token system, video, deliver usage playback uploads. Oh, those have uploads. And then I can retrieve a single upload using the upload ID. There we go.
That's what I was looking for. So as I said, this is my first time doing this. I don't really know if it's possible, but I think it should be. There we go. So this should give us back the direct upload.
If there is no direct upload, we can throw a new error, TRPC error here as well with the code of bad request because this technically is the same as this, right? There's nothing we can go now. And now let's find an asset using await mux video. And this time let's find the assets, retrieve, direct upload, and we should in here have the asset ID. So we can check if we don't have direct upload or if there is no direct upload asset ID.
So if we have this, then we have the asset, right? In case there is no asset, well, we can just go ahead and throw the same error again because there's nothing much we can do. But if we finally have the asset, we can do const updatedVideo to be await database update videos. And let's set the following values. Max status will be the asset status.
And max playback ID will be asset playback IDs. First one in the array, id, like this. Id doesn't exist, Let me just see the documentation. For fetching an asset, when I retrieve a single asset, we have playback IDs. ID doesn't exist.
Okay. Let me see inside of my video asset ready. How do I fetch this playback ID? How do I generate it? So I generate it like this.
This would be asset. And I can actually do this. If there is no play, actually, no, it's okay. If there's no playback ID, That's fine. Let's just do returning here and let's return updated video.
There's an important mistake I've made in this query. I have completely forgotten to add the WHERE clause. Just like in the screenshot, make sure you add the WHERE clause as this will be the fix I'm going to do later in some chapter. But I want to give you this fix in advance so you don't have any broken code going along. And let's confirm with my route here so when the video asset is ready what do we do?
We generate thumbnails but there's no need to do that here because we have a separate restore button here for the thumbnail. So no need to do the whole thing here. But besides that, let's see, what do we do? We add, oh, we add the duration as well. Okay.
Okay. I see. So what we're going to do is we're also going to add max asset ID, and let's go ahead and use asset ID here, and duration, and let's define the duration here as well. So duration. We are basically copying parts of what our web hook is usually responsible for and in this case we are doing that manually.
There we go. So now we have an endpoint to manually check what's going on with a certain asset. Now, I have not tested this, so I'm not sure if this will work, but what it does basically is it finds the existing video. If there is a missing upload ID, it throws a bad request. Then it uses the upload ID to find the direct upload, or we can, well, we can maybe call this upload, right?
No need to call this direct upload, like this. Simply because, It calls the upload simply because that's the only reliable field we have and then we use the asset ID here. If we are able to fetch the asset, Then we're going to go ahead and get the playback ID and the duration and also the new stages. So technically it should work. Looking at this code, this should work fine.
Now let's go inside of the FormSection component, inside of Studio, Sections, FormSection here. And similarly to our... Let's find our Remove method, I will now just add our revalidate method. TRPC videos revalidate. On success utils studio get many invalidate and let's also get get one invalidate and pass in the video the ID to be our video ID.
I think that's the prop. And this will be video revalidated. And no need to push anywhere. So that's our revalidate method here. And let's now add it to our drop-down.
That's the only thing I believe we can do. So I'm going to add it here, revalidate. And let's call this Revalidate. And I'm not sure what to do here. Rotate CCV icon.
Yeah, the same one we're using already. So now, there is nothing wrong with this video, right? But if I click Revalidate here, it says Video Revalidated, and I don't think anything should really change. It still says Ready. Everything seems to be fine.
I should still be able to play this video. Let me try and click here. There we go. The subtitles are still here. So this part seems to be working just fine.
So now how I want to test this is in the following way. Oh, we also have to add views, comments and likes here. I forgot about that. But I want to test it in the sense that I will go to my Drizzle Studio and I find this video and let me pretend like something's going on here. So I'm gonna pretend that my max status was in preparing, and I'm gonna pretend that I never got, actually no, I'm gonna pretend that I never got my max playback ID, meaning that the video could never be played.
I'm going to pretend that this happened, right? So now you can see that this says preparing, which means that in here, the user should still see this video is being processed. And let's say that no matter how much, how many time we spent waiting, this never goes to ready. What we should be able to do now is click revalidate. And hopefully now it goes to ready.
And hopefully my Drizzle Studio should now upload, so it should now change both the mux status to ready, as well as mux playback ID. Now The question stands here, what should we do with Mux track status? Because that's also a thing that could get stuck within our webhooks. So should we handle that as well in the Revalidate? Let's see inside of Route here.
And when we work with Mux asset track ready. So the only thing we need is the asset ID, it seems. Let's look at the Mux docs for this. So this is going a bit slower because as I said, I have not done this myself. I'm not sure if we have the ability to do that.
Transcription vocabularies, retrieve a transcription vocabulary. We need the vocabulary ID, we don't have that, so I don't think that's it. And they can't find the transcriptions here. I'm basically looking at tracks. Maybe if I click on an asset and if I retrieve an asset, maybe, let's see, playback IDs.
I can't see anything regarding track here. Oh, I do, tracks. Type audio. I do see tracks here, but I'm not sure what to do with them. I'm not sure which track ID should I choose.
Should I look for type audio and then get the ID of that maybe? I'm not sure. This might be something for you to research. You know, for example, After you get the asset here, here's a little to do for you. To do, potentially find a way to revalidate track status as well.
The end track status as well. Because right now I'm not entirely sure how I would do that. It's not crucial that we do it. Oh maybe update a master track, create an asset track. I do think it's possible.
Retrieve asset input info. Yes, I'm not entirely sure about it. So I will end here in regards to my solution for a potential conflict in webhooks. So we have a manual way of revalidating a video in case it gets stuck. But I don't really have a solution if the subtitles get stuck for now.
Though I'm certain there is a solution. Alright, so I think that we can now cross off all of these things from our list. We added these things for manual video revalidation. Now let's add proper category id to query suggestions and then we will finally be able to get to our search page. I know this chapter is called search page but I do want to solve those things first.
So now that we have this that's fine that's great let's go inside of source inside of our modules inside of suggestions procedures right here and inside of here we have an end query for our getManyBase procedure which basically loads the video similar in the same category, right? Or if no category it will just load videos with no category. So what happens is if I go and click on this video, I get the exact same video here because they have the same category. One thing that we need to do is we need to find, we need to ensure that whatever video will be loaded in the suggestions is not the existing video. We can do that by adding NOT from Drizzle ORM.
So NOT equals videos ID to existing video ID and add a comma here. So make sure you have imported not from Drizzle ORM. And there we go. Now, there should be no suggestions here. So go ahead and test it out like this.
Make sure that you either have no category selected or make sure both of your videos which you're going to add have the same category now. So I just added comedy here. So I will upload a new video now. It's going to work exactly the same here. And I'm going to rename this one to, this should be a suggestion like this.
And I will set the category to comedy as well. And I will click save. Looks like we got the status ready. And we also got the thumbnail. In case this got stuck, we could have always just clicked Revalidate, which is great.
I really like this solution of ours. And let's go inside of Content now, and let's click on Untitled, and let's click here. And there we go. We can now only see the relevant category videos. But if I were to change this to education and click Save and go back here, then this should no longer be a suggestion.
There we go. When I refresh that is no longer a suggestion. Excellent, so that works. Now, what I want to do, I think I've noticed something. So we also want our suggestions to only load public videos as well.
So let's include that. So besides this, let's also add equals videos. Visibility needs to be public. So now, regardless if all of our videos have the exact same category. So both of these two videos are now in comedy.
So technically, they should be suggesting one another. That will not happen because both of them are actually private. So if I now go ahead and change both of my videos to public, this one, this one is public, perfect, and I will now change this one to public as well. Now I would expect both of these to be a suggestion to one another. Excellent.
So we resolve this now. I incorrectly wrote what we have to do this. We had the proper category ID. It was just wasn't properly suggesting. Great, and just one more thing I wanna do before we go into the search page, and that is add a loading skeleton for the suggestions.
So let's go inside of our suggestions section here. We call this suggestions section. Let's go ahead and do what we usually do. This will be called suggestions section suspense. Let's export cons suggestions section here.
Let's go ahead and include the props, video ID and is manual. And let's return suspense from react error boundary, from react error boundary and suggestions section suspense and passing the video ID and passing is manual is manual there we go now for both of these let's add a fallback And it should still work just fine. And now you should see three different loadings. One here, one here, and one here. So three different things loading at the same time both being pre-fetched and now we just have to add a pretty skeleton here so let's go ahead from our most inner components and build the skeleton outwards let's go go inside of our VideoThumbnail component right here.
VideoThumbnail. And besides exporting VideoThumbnail, let's export const VideoThumbnailSkeleton. Let's return a div with a class name, relative, full, width, overflow, hidden, transition, all, group, hover, rounded, none, rounded, extra large, and aspect video. And inside a skeleton component, from components UI skeleton, and give it a class name of size full. That's it for our thumbnail skeleton.
So now we will be wherever we use thumbnail skeleton, which is inside of our grid cards, we will simply use this to then granularly build the skeleton. So let's see, relative, width, full, overflow, hidden, transition, all. We actually don't need any transitions, nor do we need group hovers here, because this is a skeleton, right? So I think this should just be enough. Great.
Now let's go inside of our VideoInfo component, because we will be using this in VideoGridCard. So this is a small component. Let's quickly build the skeleton for it here let's add a div with a class name flex and gap3 let's add a skeleton component from components ui skeleton make sure you have added an import for this and let's give this a class name of size 10 flex shrink 0 or simply shrink 0 and rounded full Let's add a div here with two skeleton components. Let's give the div a class name of a minimum width of 0, flex1 and space y of 2. Let's go ahead and give both of these similar class names, Height of 5 and width of 90%.
And let's change the lower one to be a bit shorter just to add some difference between the lines we are representing. Great. So now what we have to do is we have to build the video grid card skeleton. So let's go inside of video grid card and let's export const video grid card skeleton. And we're going to add a div here with the class name, flex, flex column, gap to, full width, like this, and then video thumbnail skeleton, and video info skeleton.
And that's it. So we built the skeletons inside of this component, so we don't have to build them here. Perfect! And now we have to do the same thing for the video row card skeleton which is a little bit more complicated simply because it has two variants. It has a variant of compact and a variant of default.
So let's go ahead and build the video roll card skeleton. Let's give this div a class name of VideoRollCardVariants and pass in the size. And we have to assign this here. Oops. And pass in the size.
There we go. Now, this will be thumbnail skeleton simply so it's easier to follow. Div and render the thumbnail skeleton, video thumbnail skeleton component and give this a class name of thumbnail variance and pass in the size. Make sure you have imported the video thumbnail skeleton from .slash video thumbnail like this. And now we will be building the info skeleton.
So the info skeleton will have a div with a class name of flex1 and a minimum width of zero. Now inside, we will have a class name of FlexJustifyBetween and the gap x of 2, another div with a class name Flex1 and a minimum width of 0 and then a skeleton component. We already have the skeleton component imported I believe here. Let me just go inside of this first skeleton component which will have a class name of the following. Use the CNUtil.
Default class names will be height 5 and width of 40% and then if size is equal to compact in that case we're going to change it to height 4 and the width of 40% to represent the compact version Make sure you have the CN import right above. And now below this, we're going to do if size is equal to default. In that case, we will render a skeleton fragment or the default variant having height four and width of 20% with a margin top of 1 and then a div here with two skeletons. The div itself will have a class name of flex, item center, gap 2 and my of 3. Both of these will have a class name the first one will be size 8 and rounded full and the lower one will have a class name of height 4 and a width of 24 there we go And then in here we will have another size checking for compact.
If that's the case, we can just return skeleton. We don't even need the fragment actually. You can remove it if you want to and give this a margin top of 1. It's never really educational to build these skeletons, but they do make the app look much, much nicer. Now that we finally have video roll card skeletons and video grid card skeletons, we can go back inside of our suggestions section right here and we can properly build the fallback.
And let's go ahead and do const suggestions section skeleton and go ahead and return a fragment. First one will be for desktop, being hidden on small devices, visible on medium devices, and a space Y of three. Array from. Let's render eight elements. Map, skip the first argument, go straight to the index here and video roll card skeleton component and passing the key of index and the variant, my apologies, size of compact.
Make sure you have imported video roll card skeleton and looks like I did something incorrectly when creating this because video roll card skeleton is expecting data to be passed here. So let me just check. We can just do this. There we go. So now it shouldn't expect any data.
Perfect. So we have this. And now let's go outside of this div and give this div a class name of blockMdHidden space Y10 like this You can copy this and paste it here And this will use the video grid card skeleton. And no size here. And now you can use the suggestion skeleton in the loading area.
And now when you do a hard refresh, there we go You should be seeing a much nicer skeleton here You can decide how many elements you want to render If you think 8 is too much, you can try with 6 Maybe that will look better There we go, perfect And now I believe that we are ready to go ahead and build our search results. So let's go ahead and go inside of our app folder, and let's go inside of home. And inside of home, we are going to create search, and then a page dsx. Let's export const dynamic to be force dynamic. And let's go ahead and create an interface page props, which will include search params.
Search params will be a promise and is going to include a query, which will be a string and the category ID, which will be string or undefined. Now let's go ahead and do an SFC of page here. Let's assign the page props. Let's get the search params. Let's make this an asynchronous component.
And let's get the query and the category ID from await search params like this. And let's go ahead and turn the div searching for query in category ID. There we go. So now you should be able to go to slash search. Say if I go ahead and visit my localhost 3000 slash search, I should see searching for nothing in category, nothing.
So technically this should be undefined as well, I suppose, because it's possible that the query is undefined. And let's go ahead and now enable our search input to actually do this. Because remember, we built this search input, but it doesn't really do anything. So let's go ahead inside of search input. It's located inside of our modules home UI components home navbar search input.
We have a lot of to-dos here, right? So this is what I suppose, what I recommend we do. We have to find a way to redirect to a place once we search something. So let's start by setting the search query here. Const searchQuery.
We can just use query, I think. Actually, let's use a search query or maybe value, set value, use state from react by default it's going to be an empty string for now like this. Then let's go ahead and let's do const handle search which is going to have an event react form event HTML form element Let's prevent default here And Let's create a new URL by leading to slash search, our newly created route. And let's go ahead and pass process environment, Vercel URL here. Now let's get const new query here to be value trim.
And let's do URL search params set query and code URI component new query. This, And let's check if new query is equal to an empty string, we will then delete query because no need to even add it. Let's confirm by doing set value to be that new query, which we just trimmed, and do router, which we have to now push here. Router, use router from next navigation. Router push to URL to string.
There we go. So this should now generate the... Basically, when you use the new URL constructor here, you have to pass the origin. You can do this in two ways. You can do window location origin, but I'm pretty sure Next.js doesn't really recommend this.
If I go ahead and click on window, whoops, Next.js. If I click on window here, I think I can, I'm not sure exactly where I can find this, but it can cause errors if you're using window values like window origin, let's see, window origin location. Maybe V0 will give us some information about this. Well, yes, I know that. Okay, you can try with this, or you can use what we did in a few places, which is using the Vercel URL.
So we avoid using the window because as far as I understand, that's not available in server-side rendering, meaning that it might cause hydration errors or some other errors. So I would recommend rather doing process environment, Vercel. What is the Vercel URL? And here's what I'm going to do. Because we're using Vercel URL so many times, and if you deploy somewhere else, you're going to have problems.
We're going to go inside of constant and export const app URL. And let's change that to process environment Vercel URL here. And let's add to do, change the custom and the variable if deploying outside Vercel. And now I'm going to search throughout my project in all places where I use Vercel URL. So that's in the search input right here.
And I will replace this with my constant app URL from my constants. That's the first place. Then in my form section component, so I'm going to change this to be my app URL from constants as well. And I will change it inside of the video menu component in modules, videos, UI components. Again, I'm going to change this here to app URL from my constants.
And in my client TRPC as well, so inside of TRPC client, I'm gonna change this as well. App URL. It's just this part that's confusing me a little bit now because Silvercell URL. Yeah. Yeah.
I'm a little bit confused because in here, everywhere, we are adding the protocol. So we assume that this will have the protocol and then when we define our app URL here we are adding the protocol. So I have a feeling that this might be incorrect here. So I will add a console log of our app URL to the app URL here, Which right now I think will just be undefined. Let's go ahead and go back instead of our search input, simply mark this as use client so we stop getting the error here.
It looks like I'm not getting any... I'm trying to get that console log but it's not appearing anywhere. I think because I'm not using the client instance anywhere. Maybe in the comments I will be using it. Let's try this.
I'm trying to find that console log. Oh, it's here. App URL is undefined, undefined. Let me go in here and try and search for Next.js search or cell URL. Enter at least two words.
Well, that is the two words. Maybe environment variables here. I'm trying to find the proper documentation for this so I don't teach you incorrect things here. Environment variables on Vercel. All right, I'm going to pause the video and find what I'm looking for.
There we go. So I found it at the Vercel documentation, not the Next.js documentation. That makes sense. So Vercel URL, as you can see, does not include the protocol scheme. So that's important, actually, I think.
Because if I go ahead and find the documentation for window location origin here, This does return the HTTPS. So the conclusion is, in here I have to add HTTPS in this part. HTTP or HTTPS? Like this. If app URL exists, then it's going to be this, otherwise HTTP localhost 3000.
Okay, so now let me just double check all the places that I'm using app URL to confirm that this will work fine. So I think that when we are copying it for this, this should also not cause any problems. We can check all of these things in production, but I was mostly worried about my TRPC instance here, which uses app URL, which is Vercel URL. And I was worried that my TRPC client.tsx was unnecessarily adding the protocol, but it looks like that's not true. So this should work in production as well.
We are of course, we will be able to modify this even further if we need to before production. Maybe make it a bit easier to work with because this is a little bit exhausting doing every time. But at least it's all in one constant here now, so we know where it is. All right, so that's the handle search. So now let's go ahead and add this to our form here.
On submit, handle the search, and the input will have a value of value and it will have a onChange setValue event target value. And let's give this... Well, I think that should be it. And we're now going to do one more thing. If we have the value, go ahead and render a button component from components UI button.
So make sure you've added this. And render the X icon from Lucid React. Give the button a type of button, very important. Variant of ghost, size of icon, onClick for now, we'll just do set value back to nothing and class name will be absolute right to top one and a half minus translate y one and a half and rounded full and give this a class name of text gray 500 like this. So I think that now this should be fine and let's also disable the button if not search query my apologies value trim.
So if there's nothing we can submit with, we shouldn't even allow that to be submitted from the UI side. So now if I go ahead and go here, It looks like I have some errors here. If I refresh, do I still have the errors? No, so it was a hot reload error. So now if I search for test here, I should be redirected to my search page test.
So if I go, if you can't see anything, try scrolling up. So hello world. There we go. Searching for hello world. And if I go ahead inside of my URL and manually add a category ID here.
One, two, three. There we go. Oh, I cannot extract. Oh, that's because this is my second parameter. So instead of using a question mark, I should be using an at sign.
There we go. Searching for Hello World, searching for as the, okay. So now let's go ahead and also try and preserve the category. But in order to do that, we should go to the page and we should prefetch the categories first. Great!
So we now have functional search input. So let's go inside of app, home, search page. And what we're going to do is we're going to void tRPC from the server, not from the client. We need TRPC from the server. Categories, getMany, refetch.
That's what we want to do now. Add hydrate client from TRPC server here. And now add search view, which will have the query and the category ID like this and now we're gonna go ahead and build the search view module. So let's go inside of modules. And we don't have the search, so let's create the search module.
Let's create the UI folder. And Inside, let's create the views. Let's add searchview.tsx. Let's copy the interface from here. And we are simply going to remove the promise in this case.
Let's go ahead and export const search view. Let's add page props here. Extract the... Actually, it's just going to be query and category individually, right? Like this.
And let's return a div here with a class name, maximum width of 1300 pixels, mx-auto, margin-bottom of 10, flex, flex-column, gap-y of 6, bx of 4, and padding of 2.5. And add a categories section. Do not import it because we will be having a new one. Pass in the current category id here. And now we are just going to build the categories section.
So create sections instead of search and create the categories section.tsx like this. And you could actually go inside of home, sections, and I think that you can actually copy the entire thing. Now that I think of it, they might be exactly the same. I was being careful because I wasn't sure about where they should redirect. And we are already using window location href here.
Yeah, I will do some research before we deploy about using window in Next.js so we can tell you the correct information. Here's what you can do. You can copy the entire categories section and I will just paste it inside of the newly created one here. And they have the exact same name and the exact same props. And they are calling the exact same query here.
This is the same one that we are prefetching here in the search page. So all of this should work just fine. None of this should cause any problems at all. So let me import the category section, but from the newly copied one, duplicated one category section, even though it's exactly the same. And let's now import the search view from the modules UI for module search UI of use.
So now I should be able to set all of these things and preserve my search page still. And I should be able to add a query here as well. So now, let's go ahead and let's quickly build the search procedure, which will be quite simple actually. And for the search procedure, what we can do is we can copy the studios procedure. So let's copy the Studio Server procedures here.
Let's go inside of Search. Let's create a server. And let's add the procedures here. Rename this router. Make sure you are inside of the Search module.
Don't accidentally override your Studio one. So this should now be the search router. We are not gonna have get one, so you can safely remove that. We are just gonna focus on get many, which will be a base procedure. So you can remove the protected procedure.
And we are not gonna work with the user ID at all here. And I don't think we need this at all. So no need for this query here either. What this will do now is simply load all videos in Pagination. But specifically, what we want is we want to add another input here, which will be called our query, like this.
And Let's also add category ID here. And now we should be able to combine all of those things. So Besides cursor and limit, let's extract the query and let's extract the category ID. And now what we're gonna do inside of this and, we will add I like from Drizzle ORM. So make sure you've added this import.
And inside of here, we're going to pass videos title and open back this add modulus and render a query inside, the value of query inside like this and let's also check if we have the category ID and then do equals videos category ID matches the category ID otherwise no need to query by category at all. There we go. So that is our search router. Now let's go ahead and let's add this to our TRPC routers app here. So I'm going to add search.
Search router from search server procedures. And now let's go inside of our search page where we prefetch the categories only. And let's void the TRPC search getMany prefetch. And we're going to pass in our query and our category ID because we have them here. And yes, technically the query should also be nullish.
And let me just see, it's not assignable. Maybe if I change this to null, I'm trying to find the proper option. It should be prefetch infinite, by the way, not prefetch. So that's the first mistake I made. But, oh, it's missing the limit.
That's the issue. Okay, so let's bring this back to nullish. And let's add the limit here, the default limit from constants. There we go, no more errors. Perfect.
And now we have our prefetched results. And we are going to use them right here inside of our search view. So this will be the results section and we're going to pass in the query to be query and the category id to be category id. Let's go ahead inside of sections and let's create the results section dot dsx like this. Now instead of the results section, just like we did in the duplicated categories section, we are going to call use suspense here.
So let's mark this as use client. Let's export const results section here. Let's create an interface. Results section props to accept the query. And to accept the category ID.
Let me fix this typo here. Let me assign the props. Let's extract the query and the category ID. And inside of here, we are going to patch our videos. So videos and query, actually, since we use the query as the prop, let's call this result query and let's call this results, I guess.
PRPC from the client, search, getMany, useSuspenseInfiniteQuery. Make sure that you did prefetch infinite here for the search getMany. So now search getMany is useSuspenseInfiniteQuery. In the first argument, we will pass the query, the limit, the default limit from the constants and also of course include the category id here. And now you just have to add the second argument which is the get next page param.
And now if you go ahead and return a div you should be able to see your results here. So let's go ahead and import the results section. Like this. Let me refresh this so you can see that if I search test, I don't see anything right? But in my studio, let's see, I have two public videos, one of them is called Untitled.
If I go ahead and search for Untitled, I should now find that one video and I do title Untitled, Mox status is ready. We found that one video we were talking about. Perfect. So now let's go ahead and let's render them out. And good news is we have all the components we need.
No need to build any new components here. One thing we will do is we will import isMobile from useIsMobile from hooks.isMobile which we got when we installed the Chassis.meui. So we will be using isMobile here to decide which type of card should we render. So now let's go ahead and let's import video raw card and video raw card skeleton and let's import video grid card and video grid card skeleton all from modules videos. And now depending if we are mobile or not, that's the card we are going to render.
So we're going to replace this with a fragment And then we're going to check if it's mobile. Let's go ahead and render a div with a class name. Plex, PlexColumn, Gap4, GapY of 10. And let's go over our results, pages, flat map, get the individual page, immediately return all the items from that page and then iterate over the items which is ultimately a single video and return video grid card. Give this a key of video ID and data of video.
Like this. Otherwise, if it's not mobile, Another div with a class name of flex, flex column and gap of 4. And go ahead and render the same thing so you can copy this part. We seem to be having some error here. We will resolve that.
Instead of video grid card, it will be video role card here. And now let's see what exactly are we missing here. I believe that we might be missing some inner join, maybe. We have view count, like count, dislike count. So let's see.
Inside of get many here, are we joining the user? Oh, we are not. Instead of get many here, we are not doing any of those things. We have to do those things first. So first of all I want to indent this entire procedure.
This. And now, inside of the select here, what I'm going to do is I'm going to steal the one from my suggestions procedures because I think in here we have everything we need. There we go. So first things first, let's do this. Like this, instead of an object.
So get table columns, which we can import from Drizzle ORM. Then we're going to load the user from the users schema. So import that from database schema. In order to load users at all, we will need to do an inner join. So let's make sure we do that here.
Inner join of the users. So that's one problem solved. Now, we need to add the view count, the like count, and the dislike count. So just make sure you add all of those things here. Let's import video views from database schema and video reactions from database schema.
So make sure you have all of those things here. And I will quickly just visit my results section just to see. Looks like there are no more errors going on actually. I think we are now ready to refresh our search page and give it a few more seconds. I think this might be some hot reloading error.
I might shut down my app entirely and just do Bundler on Dev all. Perhaps too many things are happening at once. And I will just visit my homepage here. And let's try searching for untitled. And there we go.
Looks like it loads right here. But some things are definitely missing from here. Some things seem to not be loading. So this is now loading video roll card. Let me just check if I add size default here.
Oh if I explicitly add size default then I can see things. Okay so let's go inside of video roll card and let us just change this to be default like this. And let's do the same thing in here for the skeleton. I assume that we don't have to do that because of this, but it looks like we have to, I guess. So now you can remove this here and it should show you everything it needs here.
Let's just confirm. Is that how, what we expect to see? I believe that this is it. It shows the user and everything and we have this here and when we click on it we should get redirected to that page. Perfect!
So we can now search for our videos here And let's just add infinite scroll here since it's so easy to add. So infinite scroll from components, infinite scroll right here. And we will add the loading skeletons of course, but let's first wrap this up. And instead of here, I'm just going to add HasNextPage, which will be our results query. Did I call this results query like this?
So results query dot HasNextPage. Then we're going to have isFetchingNextPage to be resultsQuery.isFetchingNextPage and fetchNextPage which will be resultsQuery.fetchNextPage There we go. So we're going to keep this for another chapter, but for now, this should be working well. One thing that I think will not work now is if, let's go ahead and change something for our video. I wanna change the reaction, the category here to comedy.
So just ensure it's in a category here. And if you search untitled here and click specifically on comedy, it seems to be working. But if I do this, you can see that it resets, right? So if I click on comedy, this is fine. But if I didn't search for something, I expect to still be searching within the comedy.
So we can go inside of the search input here. Yeah, and then we can basically preserve the category. But I do have some thoughts about how we should be doing that specifically because A few things have occurred to me and I'm still discussing this app URL, which I really, really don't like how we do throughout our app. So I'm just going to go ahead and close the chapter for now, because I think we did enough. We did exactly what we wanted to do.
It was to build the search page. So I'm going to go ahead and just see, did we do everything important? We created the search procedure, the search page, we pre-fetched the search page and we connected the search section to the API as well as the categories here. So I think that's good enough. We can also clear our search now.
And I'm gonna go ahead and for the next chapter, I will research about using the window object inside of Next.js. How safely can we do that? And I will think of a solution to improve this constants app URL solution. Nevertheless, great, great job and see you in the next chapter.