Skip to content

Commit

Permalink
features: live chat and infinite scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
Nayana62 committed Jan 18, 2024
1 parent bf5fdf8 commit 9d55928
Show file tree
Hide file tree
Showing 37 changed files with 1,067 additions and 141 deletions.
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
API_KEY =
REACT_APP_YOUTUBE_API = "https://youtube.googleapis.com/youtube/v3/videos?part=snippet%2CcontentDetails%2Cstatistics&chart=mostPopular&regionCode=US&key=[YOUR_API_KEY]"
REACT_APP_OFFSET_LIVE_CHAT =
REACT_APP_YOUTUBE_VIDEOS_API = "https://youtube.googleapis.com/youtube/v3/videos?part=snippet%2CcontentDetails%2Cstatistics&chart=mostPopular&regionCode=US&key=[YOUR_API_KEY]"
REACT_APP_YOUTUBE_VIDEO_API = "https://youtube.googleapis.com/youtube/v3/videos?part=snippet%2CcontentDetails%2Cstatistics&id=Ks-_Mh1QhMc&key=[YOUR_API_KEY]"
REACT_APP_YOUTUBE_SEARCH_SUGGESTIONS_API = "http:https://suggestqueries.google.com/complete/search?client=firefox&ds=yt&q=Query"
REACT_APP_YOUTUBE_CHANNEL_API = "https://youtube.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics&id=UC_x5XG1OV2P6uZZ5FSM9Ttw&key=[YOUR_API_KEY]"
REACT_APP_YOUTUBE_SEARCH_API = "https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=25&q=surfing&key=[YOUR_API_KEY]"
6 changes: 5 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import "./App.css";
import Layout from "./components/Layout";

function App() {
return <Layout />;
return (
<div className="relative">
<Layout />
</div>
);
}

export default App;
3 changes: 3 additions & 0 deletions src/assets/playlist.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 20 additions & 3 deletions src/components/ButtonsList.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import React from "react";
import { suggestions } from "../constants/suggestions";
import { useNavigate, useSearchParams } from "react-router-dom";

const ButtonsList = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const query = searchParams.get("search_query");

const handleButtonClick = (suggestion) => {
const query = suggestion.replace(" ", "+");
if (query === "All") {
navigate("/");
} else {
navigate(`/results?search_query=${query}`);
}
};

return (
<>
{suggestions.map((suggestion, id) => {
{suggestions.map((suggestion) => {
return (
<button
key={id}
className="py-2 px-3 bg-gray-200 rounded-lg mx-2 text-[14px] font-medium whitespace-nowrap"
key={suggestion}
className={`py-2 px-3 ${
query === suggestion ? "bg-black text-white" : "bg-gray-200"
} rounded-lg mx-2 text-[14px] font-medium whitespace-nowrap`}
onClick={() => handleButtonClick(suggestion)}
>
{suggestion}
</button>
Expand Down
31 changes: 31 additions & 0 deletions src/components/Channel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useEffect, useState } from "react";
import { getChannelData } from "../fetchData/getChannelData";
import NumberFormatter from "./NumberFormatter";

const Channel = ({ channelId }) => {
const [channelData, setChannelData] = useState([]);
useEffect(() => {
getChannelData(channelId, setChannelData);
// eslint-disable-next-line
}, []);

return (
<div className="flex gap-2 items-center">
<img
className="w-12 h-12 rounded-full"
src={channelData?.snippet?.thumbnails?.medium.url}
alt=""
/>

<div className="flex flex-col">
<p className=" font-semibold ">{channelData?.snippet?.title}</p>
<p className=" text-[14px] text-[#606060]">
<NumberFormatter number={channelData?.statistics?.subscriberCount} />
subscribers
</p>
</div>
</div>
);
};

export default Channel;
26 changes: 26 additions & 0 deletions src/components/ChatMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from "react";

const ChatMessages = ({ index, name, message }) => {
return (
<div className=" flex px-3 py-1 hover:bg-gray-200">
<div className=" w-6">
<svg
xmlns="http:https://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-6 h-6 cursor-pointer"
>
<path
fillRule="evenodd"
d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
clipRule="evenodd"
/>
</svg>
</div>
<p className=" font-bold px-2">{name}</p>
<p>{message}</p>
</div>
);
};

export default ChatMessages;
29 changes: 29 additions & 0 deletions src/components/Comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";

const Comment = ({ data }) => {
const { username, comment } = data;
return (
<div className="flex items-start p-2 bg-gray-200 rounded-xl mb-1">
<div className="m-2">
<svg
xmlns="http:https://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="w-10 h-10"
>
<path
fillRule="evenodd"
d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
clipRule="evenodd"
/>
</svg>
</div>
<div className=" py-2">
<p className=" text-sm font-medium">{username}</p>
<p>{comment}</p>
</div>
</div>
);
};

export default Comment;
15 changes: 15 additions & 0 deletions src/components/CommentsList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import Comment from "./Comment";

const CommentsList = ({ comments }) => {
return comments.map((comment) => (
<div key={comment.username}>
<Comment data={comment} />
<div className="ml-5 border-l border-l-black pl-2">
<CommentsList comments={comment.replies} />
</div>
</div>
));
};

export default CommentsList;
16 changes: 16 additions & 0 deletions src/components/ErrorPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

const ErrorPage = ({ error }) => {
return (
<div className="flex mt-20 justify-center items-center">
<div>
<p className=" text-2xl sm:text-5xl font-bold mb-2">
{error.code} error
</p>
<p className=" text-lg sm:text-xl">{error.message}</p>
</div>
</div>
);
};

export default ErrorPage;
115 changes: 104 additions & 11 deletions src/components/Header.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,73 @@
import React from "react";
import React, { useEffect, useState } from "react";
import Logo from "../assets/youtube_logo.jpg";
import { useDispatch, useSelector } from "react-redux";
import { cacheResults } from "../redux/cacheSlice";
import { Link, useNavigate } from "react-router-dom";

const searchSuggestionsAPI =
process.env.REACT_APP_YOUTUBE_SEARCH_SUGGESTIONS_API;

const Header = ({ toggleMenu }) => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [searchQuery, setSearchQuery] = useState("");
const [suggestions, setSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);

const searchCache = useSelector((store) => store.cache);

const handleScrollTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};

const handleSearch = (e, search) => {
e.preventDefault();
if (search !== "") {
const query = search.replace(" ", "+");
navigate(`/results?search_query=${query}`);
handleScrollTop();
setShowSuggestions(false);
setSearchQuery("");
}
};

useEffect(() => {
const timer = setTimeout(() => {
if (searchCache[searchQuery]) {
setSuggestions(searchCache[searchQuery]);
} else {
getSearchSuggestions();
}
}, 200);

return () => {
clearTimeout(timer);
};

// eslint-disable-next-line
}, [searchQuery]);

const getSearchSuggestions = async () => {
try {
const data = await fetch(searchSuggestionsAPI + searchQuery);
const json = await data.json();
setSuggestions(json[1]);

dispatch(
cacheResults({
[searchQuery]: json[1],
})
);
} catch (error) {
console.log("error", error);
}
};

return (
<header className="bg-white h-16 relative sm:fixed w-full px-4 sm:px-6 z-10 flex items-center justify-between">
<header className="bg-white h-16 relative sm:fixed w-full px-4 sm:px-6 z-40 flex items-center justify-between">
<div className="flex items-center">
<div onClick={toggleMenu}>
<svg
Expand All @@ -21,19 +85,35 @@ const Header = ({ toggleMenu }) => {
/>
</svg>
</div>
<img
src={Logo}
alt="youtube_logo"
className="h-14 ml-0 sm:ml-4 cursor-pointer"
/>
<Link to={"/"}>
<img
src={Logo}
alt="youtube_logo"
className="h-14 ml-0 sm:ml-4 cursor-pointer"
/>
</Link>
</div>
<div className="flex-1 flex justify-center">
<form className="flex-1 flex justify-center relative">
<input
type="text"
placeholder="Search"
className="w-8/12 sm:w-5/12 py-1 px-2 sm:px-2 sm:py-2 rounded-l-full border border-[#ccc] outline-1 outline-blue-700"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onFocus={() => setShowSuggestions(true)}
onBlur={() => {
// Delay hiding suggestions to allow time for click events
setTimeout(() => setShowSuggestions(false), 200);
}}
className="w-8/12 sm:w-5/12 py-1 px-2 sm:px-3 sm:py-2 xl:ml-16 rounded-l-full border border-[#ccc] outline-1 outline-blue-700"
/>
<button className=" hover:bg-gray-200 rounded-r-full sm:py-2 px-4 border border-[#ccc]">
<button
onClick={(e) => handleSearch(e, searchQuery)}
className="hidden"
/>
<button
onClick={(e) => handleSearch(e, searchQuery)}
className=" hover:bg-gray-200 rounded-r-full sm:py-2 px-4 border border-[#ccc] "
>
<svg
xmlns="http:https://www.w3.org/2000/svg"
fill="none"
Expand All @@ -49,7 +129,20 @@ const Header = ({ toggleMenu }) => {
/>
</svg>
</button>
</div>
{showSuggestions && (
<div className="absolute z-50 bg-white top-10 sm:top-14 left-1/2 -translate-x-1/2 w-[8rem] xs:w-[12rem] sm:w-[17rem] md:w-[20rem] lg:w-[26rem] xl:w-[30rem] 2xl:w-[34rem] rounded-lg shadow-xl border-x border-gray-400">
{suggestions.map((suggestion) => (
<p
className="px-5 py-2 hover:bg-gray-200 cursor-pointer"
key={suggestion}
onClick={(e) => handleSearch(e, suggestion)}
>
{suggestion}
</p>
))}
</div>
)}
</form>
<div>
<svg
xmlns="http:https://www.w3.org/2000/svg"
Expand Down
8 changes: 4 additions & 4 deletions src/components/Layout.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import Header from "./Header";
import SearchButtons from "./SearchButtons";
import { Outlet, useLocation } from "react-router-dom";
import WatchSidebar from "./WatchSidebar";
import SidebarMenu from "./SidebarMenu";
Expand All @@ -23,15 +22,16 @@ const Layout = () => {
<div className="">
<Header
toggleMenu={
location.pathname === "/" ? handleToggleMenu : handleToggleWatchMenu
location.pathname === "/watch"
? handleToggleWatchMenu
: handleToggleMenu
}
/>
<div>
<div className="hidden sm:block">
{location.pathname === "/" ? <SidebarMenu /> : <WatchSidebar />}
{location.pathname === "/watch" ? <WatchSidebar /> : <SidebarMenu />}
</div>
<div>
{location.pathname === "/" && <SearchButtons />}
<Outlet />
</div>
</div>
Expand Down
Loading

0 comments on commit 9d55928

Please sign in to comment.