From 7438d26fa8c4819b83a89aa5770d937f978a0c85 Mon Sep 17 00:00:00 2001
From: Guus van Meerveld
Date: Wed, 13 Mar 2024 21:18:52 +0100
Subject: [PATCH] started on search page
---
package.json | 1 +
src/app/(trending)/VideoCard.tsx | 2 +-
src/app/results/SearchPage.tsx | 17 ++++++
src/app/results/page.tsx | 12 +++++
src/app/watch/Watch.tsx | 12 +++++
src/app/watch/page.tsx | 12 +++++
src/client/adapters/index.ts | 5 +-
src/client/adapters/invidious/index.ts | 31 ++++++++++-
src/client/adapters/invidious/transformer.ts | 6 +++
.../invidious/typings/search/suggestions.ts | 10 ++++
src/client/adapters/piped/index.ts | 25 ++++++++-
src/client/index.ts | 23 ++++++--
src/client/typings/search/suggestions.ts | 1 +
src/components/Nav.tsx | 1 +
src/components/Search.tsx | 52 +++++++++++++++++++
yarn.lock | 5 ++
16 files changed, 205 insertions(+), 10 deletions(-)
create mode 100644 src/app/results/SearchPage.tsx
create mode 100644 src/app/results/page.tsx
create mode 100644 src/app/watch/Watch.tsx
create mode 100644 src/app/watch/page.tsx
create mode 100644 src/client/adapters/invidious/typings/search/suggestions.ts
create mode 100644 src/client/typings/search/suggestions.ts
create mode 100644 src/components/Search.tsx
diff --git a/package.json b/package.json
index 8910c9f..50165a1 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.0.1",
+ "use-debounce": "^10.0.0",
"zod": "^3.22.4"
},
"devDependencies": {
diff --git a/src/app/(trending)/VideoCard.tsx b/src/app/(trending)/VideoCard.tsx
index f8370e9..add1bae 100644
--- a/src/app/(trending)/VideoCard.tsx
+++ b/src/app/(trending)/VideoCard.tsx
@@ -31,7 +31,7 @@ export const VideoCard: Component<{ data: TrendingVideo }> = ({
{video.title}
-
+
{video.author.name}
diff --git a/src/app/results/SearchPage.tsx b/src/app/results/SearchPage.tsx
new file mode 100644
index 0000000..95ca165
--- /dev/null
+++ b/src/app/results/SearchPage.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { Search } from "@/components/Search";
+import { Component } from "@/typings/component";
+import { useSearchParams } from "next/navigation";
+
+export const SearchPage: Component = () => {
+ const searchParams = useSearchParams();
+
+ const query = searchParams.get("search_query");
+
+ return (
+ <>
+
+ >
+ );
+};
diff --git a/src/app/results/page.tsx b/src/app/results/page.tsx
new file mode 100644
index 0000000..0a49eb4
--- /dev/null
+++ b/src/app/results/page.tsx
@@ -0,0 +1,12 @@
+import { Suspense } from "react";
+import { SearchPage } from "./SearchPage";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/src/app/watch/Watch.tsx b/src/app/watch/Watch.tsx
new file mode 100644
index 0000000..83edc90
--- /dev/null
+++ b/src/app/watch/Watch.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { Component } from "@/typings/component";
+import { useSearchParams } from "next/navigation";
+
+export const Watch: Component = () => {
+ const searchParams = useSearchParams();
+
+ const videoId = searchParams.get("v");
+
+ return <>>;
+};
diff --git a/src/app/watch/page.tsx b/src/app/watch/page.tsx
new file mode 100644
index 0000000..4f7cbf9
--- /dev/null
+++ b/src/app/watch/page.tsx
@@ -0,0 +1,12 @@
+import { Suspense } from "react";
+import { Watch } from "./Watch";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/src/client/adapters/index.ts b/src/client/adapters/index.ts
index 55d1587..f072ae5 100644
--- a/src/client/adapters/index.ts
+++ b/src/client/adapters/index.ts
@@ -1,7 +1,10 @@
-import { TrendingVideo } from "../typings/trending";
+import { Suggestions } from "@/client/typings/search/suggestions";
+import { TrendingVideo } from "@/client/typings/trending";
export interface ConnectedAdapter {
getTrending(region: string): Promise;
+
+ getSearchSuggestions(query: string): Promise;
}
export default interface Adapter {
diff --git a/src/client/adapters/invidious/index.ts b/src/client/adapters/invidious/index.ts
index d23d6e0..6383650 100644
--- a/src/client/adapters/invidious/index.ts
+++ b/src/client/adapters/invidious/index.ts
@@ -1,14 +1,20 @@
import ky from "ky";
import Trending, { TrendingModel } from "./typings/trending";
+import Suggestions, { SuggestionsModel } from "./typings/search/suggestions";
+
import Adapter, { ApiType } from "@/client/adapters";
+
import Transformer from "./transformer";
-const apiPath = (path: string): string => `/api/v1/${path}`;
+import path from "path";
+
+const apiPath = (...paths: string[]): string =>
+ path.join("api", "v1", ...paths);
export type TrendingVideoType = "music" | "gaming" | "news" | "movies";
-export const getTrending = async (
+const getTrending = async (
baseUrl: string,
region?: string,
type?: TrendingVideoType
@@ -32,6 +38,23 @@ export const getTrending = async (
return data;
};
+const getSearchSuggestions = async (
+ baseUrl: string,
+ query: string
+): Promise => {
+ const url = new URL(apiPath("search", "suggestions"), baseUrl);
+
+ const response = await ky.get(url, {
+ searchParams: { q: query }
+ });
+
+ const json = await response.json();
+
+ const data = SuggestionsModel.parse(json);
+
+ return data;
+};
+
const adapter: Adapter = {
apiType: ApiType.Invidious,
@@ -39,6 +62,10 @@ const adapter: Adapter = {
return {
getTrending(region) {
return getTrending(url, region).then(Transformer.trending);
+ },
+
+ getSearchSuggestions(query) {
+ return getSearchSuggestions(url, query).then(Transformer.suggestions);
}
};
}
diff --git a/src/client/adapters/invidious/transformer.ts b/src/client/adapters/invidious/transformer.ts
index 4bf506b..28e608d 100644
--- a/src/client/adapters/invidious/transformer.ts
+++ b/src/client/adapters/invidious/transformer.ts
@@ -1,6 +1,8 @@
import { TrendingVideo } from "@/client/typings/trending";
import InvidiousTrending from "./typings/trending";
+import InvidiousSuggestions from "./typings/search/suggestions";
+import { Suggestions } from "@/client/typings/search/suggestions";
export default class Transformer {
public static trending(data: InvidiousTrending[]): TrendingVideo[] {
@@ -28,4 +30,8 @@ export default class Transformer {
};
});
}
+
+ public static suggestions(data: InvidiousSuggestions): Suggestions {
+ return data.suggestions;
+ }
}
diff --git a/src/client/adapters/invidious/typings/search/suggestions.ts b/src/client/adapters/invidious/typings/search/suggestions.ts
new file mode 100644
index 0000000..25c91b5
--- /dev/null
+++ b/src/client/adapters/invidious/typings/search/suggestions.ts
@@ -0,0 +1,10 @@
+import z from "zod";
+
+export const SuggestionsModel = z.object({
+ query: z.string(),
+ suggestions: z.string().array()
+});
+
+type Suggestions = z.infer;
+
+export default Suggestions;
diff --git a/src/client/adapters/piped/index.ts b/src/client/adapters/piped/index.ts
index 1897a0a..d9d711c 100644
--- a/src/client/adapters/piped/index.ts
+++ b/src/client/adapters/piped/index.ts
@@ -1,3 +1,4 @@
+import z from "zod";
import ky from "ky";
import Adapter, { ApiType } from "@/client/adapters";
@@ -5,8 +6,9 @@ import Adapter, { ApiType } from "@/client/adapters";
import Trending, { TrendingModel } from "./typings/trending";
import Transformer from "./transformer";
+import { Suggestions } from "@/client/typings/search/suggestions";
-export const getTrending = async (
+const getTrending = async (
apiBaseUrl: string,
region = "US"
): Promise => {
@@ -23,6 +25,23 @@ export const getTrending = async (
return data;
};
+const getSearchSuggestions = async (
+ apiBaseUrl: string,
+ query: string
+): Promise => {
+ const url = new URL("suggestions", apiBaseUrl);
+
+ const response = await ky.get(url, {
+ searchParams: { query: query }
+ });
+
+ const json = await response.json();
+
+ const data = z.string().array().parse(json);
+
+ return data;
+};
+
const adapter: Adapter = {
apiType: ApiType.Piped,
@@ -30,6 +49,10 @@ const adapter: Adapter = {
return {
getTrending(region) {
return getTrending(url, region).then(Transformer.trending);
+ },
+
+ getSearchSuggestions(query) {
+ return getSearchSuggestions(url, query);
}
};
}
diff --git a/src/client/index.ts b/src/client/index.ts
index e8fc346..1b151c0 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -3,7 +3,8 @@ import { TrendingVideo } from "./typings/trending";
import InvidiousAdapter from "./adapters/invidious";
import PipedAdapter from "./adapters/piped";
-import Adapter, { ApiType } from "./adapters";
+import Adapter, { ApiType, ConnectedAdapter } from "./adapters";
+import { Suggestions } from "./typings/search/suggestions";
export interface RemoteApi {
type: ApiType;
@@ -24,7 +25,7 @@ export default class Client {
this.apis = apis.map((api) => ({ ...api, score: 0 }));
}
- private getAdapterForApiType(apiType: ApiType): Adapter {
+ private findAdapterForApiType(apiType: ApiType): Adapter {
const adapter = this.adapters.find((adapter) => adapter.apiType == apiType);
if (adapter === undefined)
@@ -39,11 +40,23 @@ export default class Client {
return this.apis[randomIndex];
}
- public async getTrending(region: string): Promise {
+ private getBestAdapter(): ConnectedAdapter {
const api = this.getBestApi();
- const adapter = this.getAdapterForApiType(api.type);
+ const adapter = this.findAdapterForApiType(api.type);
+
+ return adapter.connect(api.baseUrl);
+ }
+
+ public async getTrending(region: string): Promise {
+ const adapter = this.getBestAdapter();
+
+ return await adapter.getTrending(region);
+ }
+
+ public async getSearchSuggestions(query: string): Promise {
+ const adapter = this.getBestAdapter();
- return await adapter.connect(api.baseUrl).getTrending(region);
+ return await adapter.getSearchSuggestions(query);
}
}
diff --git a/src/client/typings/search/suggestions.ts b/src/client/typings/search/suggestions.ts
new file mode 100644
index 0000000..81f5d8e
--- /dev/null
+++ b/src/client/typings/search/suggestions.ts
@@ -0,0 +1 @@
+export type Suggestions = string[];
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
index 4e6fe74..415cb54 100644
--- a/src/components/Nav.tsx
+++ b/src/components/Nav.tsx
@@ -12,6 +12,7 @@ import { Button } from "@nextui-org/button";
import NextLink from "next/link";
import { usePathname } from "next/navigation";
+import { Search } from "./Search";
export const Nav: Component = () => {
const navItems = [
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
new file mode 100644
index 0000000..e799786
--- /dev/null
+++ b/src/components/Search.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import { useClient } from "@/hooks/useClient";
+import { Component } from "@/typings/component";
+import { Autocomplete, AutocompleteItem } from "@nextui-org/autocomplete";
+import { useQuery } from "@tanstack/react-query";
+import { FormEventHandler, useState } from "react";
+import { useDebounce } from "use-debounce";
+
+export const Search: Component<{ initialQueryValue?: string }> = ({
+ initialQueryValue
+}) => {
+ const client = useClient();
+
+ const [searchQuery, setSearchQuery] = useState(initialQueryValue ?? "");
+
+ const [searchQueryDebounced] = useDebounce(searchQuery, 500);
+
+ const { isLoading, error, data } = useQuery({
+ queryKey: ["search", "suggestions", searchQueryDebounced],
+ queryFn: () => client.getSearchSuggestions(searchQueryDebounced),
+ enabled: searchQueryDebounced.length !== 0
+ });
+
+ const handleSubmit: FormEventHandler = (e) => {
+ // e.preventDefault();
+
+ console.log(searchQuery);
+ };
+
+ const suggestions = data ?? [];
+
+ return (
+
+ {suggestions.map((suggestion) => (
+
+ {suggestion}
+
+ ))}
+
+ );
+};
diff --git a/yarn.lock b/yarn.lock
index 0542599..ad3ffc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6446,6 +6446,11 @@ use-composed-ref@^1.3.0:
resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
+use-debounce@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.0.tgz#5091b18d6c16292605f588bae3c0d2cfae756ff2"
+ integrity sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==
+
use-isomorphic-layout-effect@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"