OPTIONAL: TailwindCSS
OPTIONAL: TailwindCSS
This portion is optional because I want this tutorial to focus more on APIs and NextJS. However, if you are interested in learning more about TailwindCSS, you can try it out on this project! TailwindCSS has shown to be a very powerful library especially when it comes to AI.
If not, you are free to use the provided TailwindCSS classes if you would like to change the appearance of your web app.
TailwindCSS is a different way of doing CSS. There are no .css files; instead, you add classes for each style you want.
For example, in regular CSS, you may make a .css file then add the class to your HTML like this:
1
2
3
4
5
.center-div {
display: flex;
justify-content: center;
align-items: center;
}
1
<div className="center-div"> Hello World </div>
In TailwindCSS, each one of these attributes is its own class that we can add straight to the HTML. For example:
1
<div className="flex justify-center items-center"> Hello! </div>
For a deeper dive, I highly suggest Fireship’s 100-second video on TailwindCSS.
What Mine Looks Like
Feel free to copy this TailwindCSS if you don’t want to spend the tutorial fighting CSS. However, I encourage you to make this your own and make it look how you want it to.
app.js
Click below to unblur the different parts of the answer
Part 1: State and Navigation Setup First, we need to set up our React state to keep track of what the user types into the search bar. We also initialize useRouter() so we can send the user to different pages when they click the buttons.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
export default function Home() {
const [input, changeInput] = useState("");
const router = useRouter();
const handleInputChange = (e) => {
changeInput(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim() !== "") {
router.push(`/search/${input}`);
}
};
// ... return statement below ...
Part 2: The Search Form Next, we build the search form. We tie the <input> value to our state, and when the user presses Enter (triggering onSubmit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ... inside Home() return statement ...
return (
<div className="w-screen h-screen flex items-center justify-center flex-col">
<div className="flex m-3 p-3">
<div className="w-fit h-fit flex items-center justify-center p-1">
<h1 className="italic text-white text-4xl">Focus</h1>
</div>
<div className="bg-red-800 flex items-center justify-center p-1 rounded-lg">
<h1 className="text-white text-4xl">Tube</h1>
</div>
</div>
<div className="mt-6 w-full flex justify-center">
<form onSubmit={handleSubmit} className="w-full max-w-2xl px-4 flex gap-2">
<input
value={input}
onChange={handleInputChange}
type="text"
className="bg-neutral-700 text-white pl-4 pr-4 py-3 rounded-full w-full text-lg outline-none focus:ring-2 focus:ring-red-800"
placeholder="Search for something..."
/>
<button
type="submit"
className="bg-red-800 text-white px-6 py-2 rounded-full text-lg hover:bg-red-700 active:bg-red-900 transition-colors duration-150 cursor-pointer"
>
Search
</button>
</form>
</div>
{/* ... navigation buttons below ... */}
QUESTION: You saw it introduced up above, but it is critical to our pages triggers. What is a <form> element? Does it change how we format the page?
Part 3: The Navigation Buttons Finally, we add our quick-navigation buttons. Since these don’t require a form submission, we can just use simple onClick events to check if the input is empty, and if not, route the user to the correct page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{/* ... inside Home() return statement ... */}
<div className="mt-10 flex flex-wrap justify-center gap-4">
<button
onClick={() => {
if (input.trim() !== "") {
router.push(`/playlist-search/${input}`);
}
}}
className="bg-neutral-600 text-white px-4 py-2 rounded-2xl hover:bg-neutral-500 active:bg-neutral-700 transition"
>
Search Playlists
</button>
<button
onClick={() => {
if (input.trim() !== "") {
router.push(`/playlists/${input}`);
}
}}
className="bg-neutral-600 text-white px-4 py-2 rounded-2xl hover:bg-neutral-500 active:bg-neutral-700 transition"
>
Search Playlist ID
</button>
<button
onClick={() => {
if (input.trim() !== "") {
router.push(`/video/${input}`);
}
}}
className="bg-neutral-600 text-white px-4 py-2 rounded-2xl hover:bg-neutral-500 active:bg-neutral-700 transition"
>
Search Video ID
</button>
</div>
</div>
);
}
Search Page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Link from "next/link";
export default function SearchPage() {
return (
<div className="w-screen min-h-screen flex flex-col items-center justify-start text-white">
<h1 className="text-4xl mt-6 mb-4">Search Results</h1>
<div className="flex flex-col items-center w-full overflow-y-auto pb-10">
<Link href="/video" className="w-1/2 max-w-3xl bg-neutral-700 rounded-2xl p-3 m-3 flex items-start gap-3">
<img
src="https://i.ytimg.com/vi/abc123/mqdefault.jpg"
alt="How to Learn JavaScript Fast"
width={160}
height={120}
className="rounded-md shrink-0 object-fill"
/>
<div className="flex flex-col justify-start h-full p-3">
<h2 className="text-lg font-semibold leading-tight mb-1"> How to Learn JavaScript Fast</h2>
<p className="text-sm text-gray-300 leading-snug">A quick guide to getting started with JavaScript.</p>
</div>
</Link>
</div>
</div>
);
}
Video Page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function Content() {
return (
<div className="h-lvh w-lvw flex flex-col items-center justify-start text-white">
<iframe
width="960"
height="540"
src="https://www.youtube.com/embed/your-youtube-video-id-here"
title="YouTube video player"
frameBorder="0"
allowFullScreen
className="rounded-lg shadow-lg"
></iframe>
</div>
);
}
Playlist Page
Since you previously implemented the playlist page on your own, I will not be providing code here. Feel free to copy from the search page or make it on your own.