Skip to content

Commit

Permalink
fix: add search function (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
sirily11 authored May 23, 2023
1 parent f01f4da commit fff29d0
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 59 deletions.
156 changes: 97 additions & 59 deletions components/auth/HeaderAuthticationButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
"use client";

import { signIn, signOut } from "next-auth/react";
import { classNames } from "@/src/classNames";
import useDebounce from "@/src/hooks/useDebounce";
import { useSearchVideoByKeyword } from "@/src/hooks/useSearchVideoByKeyword";
import { SearchVideoResponse } from "@/src/services/VideoService";
import { Combobox } from "@headlessui/react";
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import React from "react";
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import { CircularProgressBar } from "../shared/Placeholders";
import Avatar from "./Avatar";

const queryClient = new QueryClient();

export default function HeaderAuthticationButtons({
session,
balance,
Expand All @@ -16,68 +24,98 @@ export default function HeaderAuthticationButtons({
const { t } = useTranslation("common");

return (
<nav className="flex grow">
{/* Desktop sign in links */}
<ul className="flex grow justify-end flex-wrap items-center space-x-3">
<SearchBar />
{!session && (
<li>
<Link
className="font-medium text-gray-600 decoration-blue-500 decoration-2 underline-offset-2 hover:underline px-3 lg:px-5 py-2 flex items-center transition duration-150 ease-in-out"
href={"/signin"}
>
{t("sign-in")}
</Link>
</li>
)}
{!session && (
<li className="ml-3">
<Link
className="btn-sm text-white bg-blue-500 hover:bg-blue-600 w-full shadow-sm"
href="/signup"
>
{t("sign-up")}
</Link>
</li>
)}
{session && <Avatar user={session.user} tokenBalance={balance} />}
</ul>
</nav>
<QueryClientProvider client={queryClient}>
<nav className="flex grow">
{/* Desktop sign in links */}
<ul className="flex grow justify-end flex-wrap items-center space-x-3">
<SearchBar />
{!session && (
<li>
<Link
className="font-medium text-gray-600 decoration-blue-500 decoration-2 underline-offset-2 hover:underline px-3 lg:px-5 py-2 flex items-center transition duration-150 ease-in-out"
href={"/signin"}
>
{t("sign-in")}
</Link>
</li>
)}
{!session && (
<li className="ml-3">
<Link
className="btn-sm text-white bg-blue-500 hover:bg-blue-600 w-full shadow-sm"
href="/signup"
>
{t("sign-up")}
</Link>
</li>
)}
{session && <Avatar user={session.user} tokenBalance={balance} />}
</ul>
</nav>
</QueryClientProvider>
);
}

function SearchBar() {
const [selectedVideo, setSelectedVideo] = useState<SearchVideoResponse>();
const [query, setQuery] = useState("");
const debounceQuery = useDebounce(query, 500);
const { isLoading, data } = useSearchVideoByKeyword(debounceQuery);

return (
<form className="w-96 hidden sm:block">
<label className="mb-2 text-sm font-medium text-gray-900 sr-only">
Search
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg
aria-hidden="true"
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
<div className="w-96 hidden sm:block">
<Combobox value={selectedVideo} onChange={setSelectedVideo}>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg
aria-hidden="true"
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
{isLoading && (
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<CircularProgressBar />
</div>
)}
<Combobox.Input
onChange={(event) => setQuery(event.target.value)}
className="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
</div>
<input
type="search"
id="default-search"
className="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search videos"
required
/>
</div>
</form>
{(data?.length ?? 0) > 0 && (
<Combobox.Options
className={
"absolute z-10 mt-1 max-h-60 w-96 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
}
>
{data?.map((video) => (
<Combobox.Option
key={video.id}
value={video}
className={({ active }) =>
classNames(
"relative cursor-default select-none py-2 pl-3 pr-9",
active ? "bg-blue-600 text-white" : "text-gray-900"
)
}
>
{video.title}
</Combobox.Option>
))}
</Combobox.Options>
)}
</Combobox>
</div>
);
}
25 changes: 25 additions & 0 deletions src/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";

export default function useDebounce(value: string, delay: number) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);

return debouncedValue;
}
11 changes: 11 additions & 0 deletions src/hooks/useSearchVideoByKeyword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useQuery } from "react-query";
import { TransactionService } from "../services/TransactionService";
import { VideoService } from "../services/VideoService";

export function useSearchVideoByKeyword(key: string) {
const query = useQuery(["video", key], () => {
return VideoService.searchVideo(key);
});

return query;
}
19 changes: 19 additions & 0 deletions src/services/VideoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import axios from "axios";
import { SignedUrl } from "./StorageService";
import { Profile } from "./AuthenticationService";
import { GetCategoryResponse } from "./CategoryService";
import { z } from "zod";

export enum VideoStatus {
UPLOADING = "UPLOADING",
Expand Down Expand Up @@ -97,6 +98,14 @@ export interface GetMyVideoByIdDto extends GetVideoResponse {
passedStatus: string[];
}

export const SearchVideoSchema = z.object({
id: z.string(),
title: z.string(),
thumbnail: z.string().url(),
});

export type SearchVideoResponse = z.infer<typeof SearchVideoSchema>;

export class VideoService {
static async createVideo(
accessToken: string,
Expand Down Expand Up @@ -212,4 +221,14 @@ export class VideoService {
});
return video.data;
}

/**
* Search video by title
*/
static async searchVideo(key: string): Promise<SearchVideoResponse[]> {
if (key.length === 0) return [];
const url = process.env.NEXT_PUBLIC_API_ENDPOINT + `/video/search/${key}`;
const video = await axios.get(url);
return SearchVideoSchema.array().parse(video.data);
}
}

1 comment on commit fff29d0

@vercel
Copy link

@vercel vercel bot commented on fff29d0 May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.