diff --git a/src/app/results/Channel.tsx b/src/app/results/Channel.tsx
index 16b64de..0968c07 100644
--- a/src/app/results/Channel.tsx
+++ b/src/app/results/Channel.tsx
@@ -7,7 +7,7 @@ import { Card, CardBody } from "@nextui-org/card";
import { Image } from "@nextui-org/image";
import Link from "next/link";
import NextImage from "next/image";
-import formatViewCount from "@/utils/formatViewCount";
+import formatBigNumber from "@/utils/formatBigNumber";
export const Channel: Component<{ data: ChannelProps }> = ({ data }) => {
const url = `/channel/${data.id}`;
@@ -32,9 +32,9 @@ export const Channel: Component<{ data: ChannelProps }> = ({ data }) => {
{data.name}
-
{formatViewCount(data.subscribers)} subscribers
+ {formatBigNumber(data.subscribers)} subscribers
{data.videos !== 0 && (
- {formatViewCount(data.videos)} videos
+ {formatBigNumber(data.videos)} videos
)}
{data.description}
diff --git a/src/app/results/Filter.tsx b/src/app/results/Filter.tsx
new file mode 100644
index 0000000..330ead1
--- /dev/null
+++ b/src/app/results/Filter.tsx
@@ -0,0 +1,56 @@
+import { SearchType } from "@/client/typings/search/options";
+import { Component } from "@/typings/component";
+
+import { FiFilter as FilterIcon } from "react-icons/fi";
+
+import { Button } from "@nextui-org/button";
+
+import {
+ Dropdown,
+ DropdownTrigger,
+ DropdownMenu,
+ DropdownItem
+} from "@nextui-org/dropdown";
+import { useMemo } from "react";
+
+export const Filter: Component<{
+ filter: SearchType;
+ setFilter: (filter: SearchType) => void;
+}> = ({ setFilter, filter }) => {
+ const filterMenuItems: { key: SearchType; label: string }[] = useMemo(
+ () => [
+ { key: "all", label: "All" },
+ { key: "video", label: "Videos" },
+ { key: "channel", label: "Channels" },
+ { key: "playlist", label: "Playlists" }
+ ],
+ []
+ );
+
+ return (
+
+
+
+
+ {
+ const selectedKeys = keys as Set;
+
+ const selectedKey = Array.from(selectedKeys)[0];
+
+ if (!selectedKey) return;
+
+ setFilter(selectedKey);
+ }}
+ >
+ {(item) => {item.label}}
+
+
+ );
+};
diff --git a/src/app/results/Search.tsx b/src/app/results/Search.tsx
index 6523882..0f2ff65 100644
--- a/src/app/results/Search.tsx
+++ b/src/app/results/Search.tsx
@@ -5,49 +5,74 @@ import { useClient } from "@/hooks/useClient";
import { Component } from "@/typings/component";
import { Spacer } from "@nextui-org/spacer";
import { useInfiniteQuery } from "@tanstack/react-query";
-import { useSearchParams } from "next/navigation";
import { Channel } from "./Channel";
import { Container } from "@/components/Container";
import { LoadingPage } from "@/components/LoadingPage";
import { Button } from "@nextui-org/button";
import { Video } from "./Video";
import { Playlist } from "./Playlist";
-import { Fragment, useCallback } from "react";
-import { CircularProgress } from "@nextui-org/progress";
-import { useVisibility } from "reactjs-visibility";
+import { Fragment, useCallback, useMemo } from "react";
import { Loading } from "./Loading";
+import { Filter } from "./Filter";
+import { useSearchParams } from "next/navigation";
+import { SearchType, SearchTypeModel } from "@/client/typings/search/options";
+import { useSearch } from "@/hooks/useSearch";
export const Search: Component = () => {
+ const client = useClient();
+
const searchParams = useSearchParams();
- const query = searchParams.get("search_query");
+ const query = searchParams.get("search_query") as string;
- const client = useClient();
+ const invalidQuery = useMemo(() => {
+ if (query === null || query.length === 0)
+ return new Error(`The required parameter 'query' is missing`);
+ }, [query]);
+
+ const filter = (searchParams.get("filter") ?? "all") as SearchType;
+
+ const invalidFilter = useMemo(() => {
+ const parsed = SearchTypeModel.safeParse(filter);
+
+ if (!parsed.success)
+ return new Error(`The provided filter \`${filter}\` is invalid`);
+ }, [filter]);
const {
data,
- error,
+ error: fetchError,
fetchNextPage,
hasNextPage,
refetch,
isFetching,
isFetchingNextPage
} = useInfiniteQuery({
- queryKey: ["search", query],
+ queryKey: ["search", query, filter],
queryFn: async ({ pageParam }) => {
- return await client.getSearch(query ?? "", { pageParam: pageParam });
+ return await client.getSearch(query ?? "", {
+ pageParam: pageParam,
+ type: filter
+ });
},
- enabled: query !== null,
+ enabled: !!invalidQuery || !!invalidFilter,
initialPageParam: "",
getNextPageParam: (lastPage, pages) => lastPage.nextCursor
});
- const handleUserReachedPageEnd = useCallback(
- (visiblity: boolean) => {
- console.log(visiblity);
+ const error = invalidQuery ?? invalidFilter ?? fetchError ?? undefined;
- console.log(visiblity, !isFetchingNextPage, hasNextPage);
+ const searchFor = useSearch();
+ const setFilter = useCallback(
+ (filter: SearchType) => {
+ searchFor(query, filter);
+ },
+ [query]
+ );
+
+ const handleUserReachedPageEnd = useCallback(
+ (visiblity: boolean) => {
if (visiblity && !isFetchingNextPage) fetchNextPage();
},
[hasNextPage, isFetchingNextPage]
@@ -56,9 +81,16 @@ export const Search: Component = () => {
return (
<>
-
+
- {isFetching && }
+ {isFetching && !error && }
{error && (
@@ -74,33 +106,30 @@ export const Search: Component = () => {
)}
- {data?.pages.map((page, i) => {
- return (
-
- {page.items.map((result) => {
- switch (result.type) {
- case "channel":
- return ;
-
- case "video":
- return ;
-
- case "playlist":
- return ;
- }
- })}
-
- );
- })}
+ {!error &&
+ data?.pages.map((page, i) => {
+ return (
+
+ {page.items.map((result) => {
+ switch (result.type) {
+ case "channel":
+ return ;
+
+ case "video":
+ return ;
+
+ case "playlist":
+ return ;
+ }
+ })}
+
+ );
+ })}
-
- {/* {!isFetching && !isFetchingNextPage && !error && (
-
- )} */}
>
diff --git a/src/app/results/Video.tsx b/src/app/results/Video.tsx
index daba460..dd49593 100644
--- a/src/app/results/Video.tsx
+++ b/src/app/results/Video.tsx
@@ -7,7 +7,7 @@ import { Image } from "@nextui-org/image";
import NextImage from "next/image";
import { useMemo } from "react";
-import formatViewCount from "@/utils/formatViewCount";
+import formatBigNumber from "@/utils/formatBigNumber";
import formatUploadedTime from "@/utils/formatUploadedTime";
import { Link } from "@nextui-org/link";
import NextLink from "next/link";
@@ -47,7 +47,7 @@ export const Video: Component<{ data: VideoProps }> = ({ data }) => {
{data.title}
-
{formatViewCount(data.views)} views
+ {formatBigNumber(data.views)} views
{formatUploadedTime(data.uploaded)}
;
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index 5bd1255..3fa8d99 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -8,7 +8,7 @@ import { useCallback, useMemo, useState } from "react";
import { useDebounce } from "use-debounce";
import { FiSearch as SearchIcon } from "react-icons/fi";
-import { useRouter } from "next/navigation";
+import { useSearch } from "@/hooks/useSearch";
export const Search: Component<{
initialQueryValue?: string;
@@ -17,7 +17,7 @@ export const Search: Component<{
const [searchQuery, setSearchQuery] = useState(initialQueryValue ?? "");
- const router = useRouter();
+ const searchFor = useSearch();
const [searchQueryDebounced] = useDebounce(searchQuery, 250);
@@ -31,7 +31,7 @@ export const Search: Component<{
});
const submit = useCallback((query: string) => {
- router.push(`/results?search_query=${query}`);
+ searchFor(query);
}, []);
const suggestions = useMemo(
diff --git a/src/components/Video.tsx b/src/components/Video.tsx
index d08d458..d069f25 100644
--- a/src/components/Video.tsx
+++ b/src/components/Video.tsx
@@ -4,7 +4,7 @@ import { Card, CardFooter, CardBody } from "@nextui-org/card";
import { Image } from "@nextui-org/image";
import { Divider } from "@nextui-org/divider";
import Link from "next/link";
-import formatViewCount from "@/utils/formatViewCount";
+import formatBigNumber from "@/utils/formatBigNumber";
import formatDuration from "@/utils/formatDuration";
import { ContextMenu } from "./ContextMenu";
import formatUploadedTime from "@/utils/formatUploadedTime";
@@ -89,7 +89,7 @@ export const Video: Component<{ data: VideoProps }> = ({ data }) => {
- Views: {formatViewCount(data.views)}
+ Views: {formatBigNumber(data.views)}
diff --git a/src/hooks/useSearch.ts b/src/hooks/useSearch.ts
new file mode 100644
index 0000000..9cface0
--- /dev/null
+++ b/src/hooks/useSearch.ts
@@ -0,0 +1,17 @@
+import { SearchType } from "@/client/typings/search/options";
+import { useRouter } from "next/navigation";
+
+const searchPathname = "/results";
+
+export const useSearch = (): ((query: string, filter?: SearchType) => void) => {
+ const router = useRouter();
+
+ return (query, filter = "all") => {
+ const params = new URLSearchParams();
+
+ params.set("search_query", query);
+ params.set("filter", filter);
+
+ router.push(searchPathname + "?" + params.toString());
+ };
+};
diff --git a/src/utils/formatBigNumber.ts b/src/utils/formatBigNumber.ts
new file mode 100644
index 0000000..d1bc6ce
--- /dev/null
+++ b/src/utils/formatBigNumber.ts
@@ -0,0 +1,26 @@
+const billion = 1.0e9;
+const million = 1.0e6;
+const thousand = 1.0e3;
+
+const formatBigNumber = (num: number): string => {
+ const abs = Math.abs(num);
+
+ // Nine Zeroes for Billions
+ if (abs >= billion) return (abs / billion).toPrecision(3) + "B";
+
+ if (abs >= million) {
+ if (abs >= million * 10) return (abs / million).toPrecision(3) + "M";
+
+ return (abs / million).toPrecision(2) + "M";
+ }
+
+ if (abs >= thousand) {
+ if (abs >= thousand * 10) return (abs / thousand).toPrecision(3) + "K";
+
+ return (abs / thousand).toPrecision(2) + "K";
+ }
+
+ return abs.toString();
+};
+
+export default formatBigNumber;
diff --git a/src/utils/formatViewCount.ts b/src/utils/formatViewCount.ts
deleted file mode 100644
index 036f76d..0000000
--- a/src/utils/formatViewCount.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-const formatViewCount = (num: number): string => {
- // Nine Zeroes for Billions
- return Math.abs(num) >= 1.0e9
- ? (Math.abs(num) / 1.0e9).toPrecision(3) + "B"
- : // Six Zeroes for Millions
- Math.abs(num) >= 1.0e6
- ? (Math.abs(num) / 1.0e6).toPrecision(3) + "M"
- : // Three Zeroes for Thousands
- Math.abs(num) >= 1.0e3
- ? (Math.abs(num) / 1.0e3).toPrecision(3) + "K"
- : Math.abs(num).toString();
-};
-
-export default formatViewCount;