
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
👉 Bunny (create your free account and get one extra free month): https://jsm.dev/jsm-bunny
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we explore how to create a video details page using React. The focus is on fetching video details by ID and displaying them correctly using components like VideoPlayer and VideoDetailHeader.
getVideoByID
to fetch specific video details.params
to access the video ID from the URL.VideoPlayer
component to render the video.VideoDetailHeader
component to display video title, creator information, and creation date.00:00:02 To create the video details page, you can head over back to the video.ts file.
00:00:08 Yep, we'll be spending some more time right here creating a server action, not to fetch all the videos, but this time to fetch the video details of a single upload.
00:00:20 So I'll create a new export const.
00:00:24 And I'll call it get video by ID.
00:00:28 I'll wrap it with the with error handler.
00:00:31 And that'll be an async function, which accepts one single parameter.
00:00:36 I think you already know what it is.
00:00:38 It's the video ID of a type string.
00:00:41 So within it, we can say const and then the structure, a single video record out of the array of records that we're going to get back because we want to
00:00:50 get a single one here.
00:00:52 And I'll say await.
00:00:55 build videos with user query.
00:00:58 So here we're not only fetching the video, but also the user.
00:01:02 So I'll say dot where EQ, so equal, videos.id with the video ID, like this.
00:01:13 So we're basically saying, hey, give me this specific video details.
00:01:17 And then we simply want to return that video record.
00:01:21 Perfect.
00:01:23 Now, if we head back over to the video details page, so that's the one where we have the ID, video ID right here, and we want to get access to that ID
00:01:34 right here through params.
00:01:36 So you can destructure params and say it's of a type params.
00:01:42 Now, why is it params and not search params?
00:01:46 well the difference is that with search params you would have something like this URL question mark and then maybe page is equal to two and maybe filter
00:01:57 is equal to ascending like this but with params it actually looks like this URL forward slash and then a specific ID, like 123, which you can get access
00:02:10 through params.
00:02:11 So in this case, we're trying to access a specific resource, so that is params.
00:02:15 So we can destructure the video ID, and that is equal to await params.
00:02:21 Now, why is it video ID?
00:02:23 Well, that's because we call this dynamic page right here, video ID.
00:02:28 Then we can make a call by destructuring the user and the video from await get video by ID to which we need to pass the video ID.
00:02:42 If there is no video, in that case, we can just redirect over to 404. Else, we can start focusing on returning that video details.
00:02:52 But to return it, we'll need to have an iframe component that'll actually render the video.
00:02:58 So I'll create a new component within the components folder, and I'll call it a VideoPlayer.tsx.
00:03:06 And I'll run RAFCE right here.
00:03:09 And now we can try to import that video player.
00:03:13 as a self-closing component.
00:03:15 And to it, we need to pass the video ID equal to video.id.
00:03:23 Now, if you save this and go back right here, you'll see the video player.
00:03:28 And just so we know whether we're getting the correct data from the video, we can console log it, but we can also maybe just render an H1 that'll have
00:03:35 a class name equal to text-to-excel.
00:03:39 And we can maybe render the video title.
00:03:42 So I'll say video.title.
00:03:45 And now if you head back, you can see the title of this video.
00:03:49 But what matters more is actually rendering it.
00:03:51 So let's put this video player within a section.
00:03:57 that'll have a class name equal to video details.
00:04:02 And then within it, we can display a div that'll have a class name equal to content.
00:04:09 And just within it, we can display this video player.
00:04:12 Perfect.
00:04:13 So if you save this, it's still here and it's waiting to be displayed.
00:04:18 So let's head over into the video player component and let's implement it by simply returning a single iframe with a source equal to create iframe link.
00:04:32 This is coming from libutils to which we need to provide the video ID.
00:04:36 And this video ID, as we saw, is coming through props.
00:04:40 So video ID.
00:04:41 And this is of a type video player props.
00:04:46 Now, alongside passing the source to this component, we can also pass some other information, such as set the loading to lazy.
00:04:55 We can also pass over the title equal to video player for now.
00:05:03 We can pass the style equal to border zero and Z index of 50. So it appears over other elements if needed.
00:05:13 We can also allow full screen.
00:05:17 And we can also allow some things that are going to be useful on mobile devices, such as the accelerometer, gyroscope, autoplay,
00:05:26 and encrypted media.
00:05:27 Gyroscope will be useful if the users on mobile want to just rotate their screen and see it in full screen.
00:05:35 And we can also allow something called picture.
00:05:39 in picture so that people can keep playing the video even if they go visit another page.
00:05:45 Oh, and let's actually wrap this iframe within a div and we can give this div a class name equal to video player.
00:05:59 Like that.
00:06:00 So now if you go back, you'll see a nice looking centrally located 404. Let's see if the ID that we're passing in is actually a good ID.
00:06:10 So below the H1, I'll also just try to render the ID to see whether we're accessing it the right way.
00:06:17 So I'll say video.id right here.
00:06:20 And it looks like we are getting an ID that seems to be the one belonging to Bunny.
00:06:24 If you head over to Bunny once again, to stream, you will be able to see your video.
00:06:30 And you can also see its ID right here, ending in 478. But this doesn't seem to be the one.
00:06:37 This is the ID of the video stored in the database.
00:06:40 But instead, we should be comparing it with the video ID stored in Bunny.
00:06:45 So that's going to be dot video ID.
00:06:50 like this.
00:06:51 So if I go back now, you can see that it matches 47a, but we still get a 404. The reason we're getting a 404 is because if you check the iframe create
00:07:01 iframe link utility function, here I have hardcoded a random bunny library ID, but instead we need to grab a new one coming from our ENVs.
00:07:12 So head in here and take a look at your ENVs and you'll see that our current bunny library ID is a bit different.
00:07:21 For me, it's 421-774.
00:07:21 For you, it's going to be a bit different.
00:07:25 So whatever it is, just update this number right here to your Bunny Library ID.
00:07:31 And as soon as you do this and reload, you'll be able to see your video appear right here.
00:07:38 You might want to mute it in case you uploaded the same video that I have, because you'll be able to hear me two times.
00:07:44 So this is perfect.
00:07:46 We got the video playing.
00:07:47 Now alongside the video, we can also style the header just a tiny bit.
00:07:52 So head over back to your code and create a new component.
00:07:57 I'll call it the video detail header.
00:08:00 So video detail header.tsx run And then we can import it right within the video details page.
00:08:12 Here, instead of these two H1s, video detail header as a self-closing component.
00:08:18 To it, we can spread all of the properties of a video, just like this.
00:08:23 And we can head over into it and accept those props.
00:08:28 So I will simply accept everything we need.
00:08:31 The video contains the title, the created ad, the user image, the username, the video ID, the owner ID, visibility, whether it's public or private,
00:08:47 and a thumbnail URL.
00:08:49 We're getting all of these things coming from the video.
00:08:54 So that's going to be video, detail, header, props.
00:08:58 And now we can put them to use.
00:09:00 First, I will wrap everything in a header component because that's exactly what we're developing.
00:09:06 And I'll give it a class name equal to detail-header.
00:09:12 Within it, I'll create an aside component.
00:09:15 And I'll give it a class name equal to user-info.
00:09:20 So if I do this, I'll collapse it just a bit so we can see what's happening over here.
00:09:25 And within that aside, I'll render an H1 that'll render the title.
00:09:31 There we go.
00:09:32 That's already looking better.
00:09:34 Below the title, we can render a figure, and within it, we can render a button.
00:09:41 And within that button, we can render an image with the source of a user, img, or just an empty string, if it doesn't exist,
00:09:51 with an alt tag of user.
00:09:55 a width of 24, a height of 24, and a class name equal to rounded-full.
00:10:05 So we can close it right here and save it.
00:10:10 And now you can see that an image is trying to appear right here, but right now it looks like the user image is empty.
00:10:18 That's because this user image is actually coming from the users part.
00:10:22 So I'll say user img is equal to user question mark dot image and user name is also coming from the user part.
00:10:31 So I'll say user question mark dot name and also the owner ID, I believe owner ID.
00:10:38 can be equal to video.userID.
00:10:41 So this is not exactly the same like userID, userID.
00:10:44 So we have to declare it specifically like this.
00:10:47 And now we're trying to render the image from Google, and we have to add it to the host name.
00:10:53 So copy this host name right here.
00:10:56 And then head over to next config and add a second remote pattern by duplicating this one and add the host name you copied from here.
00:11:07 Or alternatively, you can just say host name is anything and that allow the images from all hosts.
00:11:16 So if you do that.
00:11:17 you'll be able to see a little JavaScript mastery icon right here, or in your case, something else.
00:11:23 Then we can give this button an on click and we simply want to push over to that user's profile.
00:11:30 So I'll get access to the router functionality.
00:11:33 Router is equal to use router coming from Next Navigation.
00:11:37 And I'll say router.push and I'm going to push to forward slash profile, forward slash owner ID to see who created this video.
00:11:47 And since we use the router here, we'll have to turn this component into a client render component.
00:11:52 So I'll say use client right at the top.
00:11:56 Perfect.
00:11:57 And I think we can use this button on our navigation bar as well because right now we still have a dummy photo here.
00:12:04 So I'll head over to our navbar.
00:12:08 And I believe that here we are rendering this dummy image.
00:12:11 Yep, we're already doing the same thing.
00:12:13 So what I'll do is I'll just modify this.
00:12:16 Now we actually have the user stored under session, so we need to extract that user by saying const destructured data and rename it as session,
00:12:28 which is equal to AuthClient.useSession.
00:12:34 And we can extract the user from that session, question mark dot user.
00:12:39 And we can remove this fake user now, and instead we can point to forward slash profile, forward slash, and then that's going to be the dollar sign,
00:12:51 curly braces.
00:12:55 And we can replace the dummy photo with the user.image right here.
00:13:01 And now if you click here, it's going to point you to your, for now, dummy profile page.
00:13:06 And if you click here, same thing happens.
00:13:09 So let's go back to the video details header and let's style it a bit more.
00:13:14 Just below this self-closing image we can render an H2 and I will render either the username or if the username doesn't exist I will simply say guest.
00:13:27 So now we can see 3JS animations.
00:13:30 Who was it created by?
00:13:31 It is JavaScript Mastery.
00:13:33 And it is clickable and pointing to the profile.
00:13:36 Now we can exit this button and we can open up a Fig caption.
00:13:41 Fig caption.
00:13:43 And here, we can render a span that'll render a class name with a margin top of 1. And here, you can just render some kind of a dash or maybe even some
00:13:53 kind of a circle that looks like this.
00:13:55 Once you do that, after that, you can render dynamically, days ago, and then pass created at.
00:14:04 So this will now say JavaScript Mastery.
00:14:06 This is maybe not the best separator I was looking for.
00:14:09 I was looking for something like this.
00:14:11 So now it makes sense.
00:14:12 Title, profile, and then when you created it.
00:14:16 Also, this is super important.
00:14:19 Outside of this aside, we can create the other side.
00:14:22 So I'll also call it aside.
00:14:24 And I'll give it a class name equal to CTA, call to action.
00:14:29 And we want to create another button that will allow to copy the URL of this video.
00:14:36 So I'll render an image with a source of forward slash assets, forward slash icons, forward slash link.svg with an alt tag of copy link,
00:14:49 a width of about 24 and a height of about 24. And that'll look like this.
00:14:56 This button will have an onClick, and it'll call the handleCopyLink function, which we can declare just above, const handleCopyLink.
00:15:09 is equal to a callback function where we use JavaScript Navigator to get access to the clipboard and want to write some text into the clipboard.
00:15:20 And the text that we will write is going to be the current location in the browser.
00:15:26 So that's going to be window.location.origin forward slash video forward slash, and then the video ID.
00:15:37 That way we'll be able to share it with somebody else as well.
00:15:40 And we can also set copied to be set to true.
00:15:44 This is a state that I want to create called copied and set copied.
00:15:51 at the start set to false so that later on, once we click on it and switch it to true, we can then display that on the button to indicate to the user that
00:16:00 it has been copied.
00:16:02 So here where we have the link, I will say if copied is true, then I will render a different asset.
00:16:12 I will render forward slash assets, forward slash images, forward slash checked.
00:16:21 else will render this assets thing that I have right here.
00:16:24 So if I save it and click it, you can see that now it turns checked.
00:16:29 Of course, we'll have to bring it back to the original state after a couple of seconds.
00:16:33 So I'll create a new use effect and we will only call it when the copied state has changed.
00:16:41 I'll check if copied is true, then we will set copied to false, but we want to do it after some time.
00:16:51 So I'll open up the setTimeout and we will do it after maybe 2000 milliseconds, just like this.
00:17:01 And this setTimeout has to be declared within a function.
00:17:05 So I'll say const.
00:17:07 change checked, like this.
00:17:10 And then in the use effect, you can also perform a cleanup, which makes sure to remove these timeouts that are no longer used.
00:17:17 So I'll say return clear timeout.
00:17:21 And then to it, you have to provide the name or the timeout that you actually stored in there, which is going to be change checked in this case.
00:17:30 Oh, and I think I forgot to actually provide a callback function for this.
00:17:34 So to properly clear it, you have to define it as an output of a callback function.
00:17:39 So now if you click it one more time, wait for one and then two seconds, it properly gets cleared and it still has been copied to the clipboard.
00:17:50 Great.
00:17:50 Now, let's see what else do we have here.
00:17:53 So we have added this copy button right within the CTA div.
00:17:57 On mobile, it appears right here, but on desktop, you can see it right here on the right side, and it looks even better.
00:18:04 So here we have the title and the author, and on the right side, you can copy the URL.
00:18:09 And if you try to share it with your friend, you'll notice that that won't quite work.
00:18:14 And that's because I again made the same mistake.
00:18:17 I was redirecting to the bunny ID, but this time we actually need the other one.
00:18:22 We needed the database ID, ending in B1B.
00:18:26 Whereas this one here is ending in the bunny ID for 7a So if I head back over to here and see what that copies It copies the video ID,
00:18:38 but instead it should have been just the ID.
00:18:40 So let's make sure that we're actually passing it Into the video detail header.
00:18:45 I think we are So I'll just destructure the ID and I'll pass it here.
00:18:51 Now, if you reload, copy it, and navigate over to it, you'll notice that now, whoever you share this link to can also access this video.
00:19:02 And Bunny's video player is super nice.
00:19:04 You can modify the volume, pause and play, of course, but you also have closed captions.
00:19:11 So it actually recognized all the captions from my voice, it transcribed the video, and you can also switch between different languages if you selected
00:19:21 automatic transcriptions.
00:19:22 You also have different qualities and speed.
00:19:26 Everything you might need.
00:19:27 as well as open it within a small video player.
00:19:31 But let's get back to the main one.
00:19:33 So, now that this video details page is looking great, what do we have to do next?
00:19:38 Well, we are now able to see the video that we have uploaded, but what do you say that we focus on the main functionality of this application,
00:19:46 which is the ability to record the video on the fly.
00:19:49 So, let's do that next.