added video and channel cards to search page
continuous-integration/drone/push Build is passing Details

nextui
Guus van Meerveld 1 month ago
parent 5ac329296e
commit b1be90d190

@ -23,14 +23,17 @@ export const Channel: Component<{ data: ChannelProps }> = ({ data }) => {
src={data.thumbnail}
alt={data.name}
as={NextImage}
className="rounded-full"
unoptimized
/>
<div className="flex-1 flex flex-col">
<div className="flex-1 flex flex-col justify-center">
<h1 className="text-lg">{data.name}</h1>
<div className="flex flex-row gap-4 items-center font-semibold text-default-600">
<h1>Subscribers: {formatViewCount(data.subscribers)}</h1>
<h1>Videos: {formatViewCount(data.videos)}</h1>
<h1>{formatViewCount(data.subscribers)} subscribers</h1>
{data.videos !== 0 && (
<h1>{formatViewCount(data.videos)} videos</h1>
)}
</div>
<p className="text-default-600">{data.description}</p>
</div>

@ -5,10 +5,15 @@ import { Image } from "@nextui-org/image";
import NextImage from "next/image";
import { useMemo } from "react";
import Link from "next/link";
import formatViewCount from "@/utils/formatViewCount";
import formatUploadedTime from "@/utils/formatUploadedTime";
import { Link } from "@nextui-org/link";
import NextLink from "next/link";
import formatDuration from "@/utils/formatDuration";
export const Video: Component<{ data: VideoProps }> = ({ data }) => {
const url = `/watch?v=${data.id}`;
const channelUrl = `/channel/${data.author.id}`;
const videoSize = 200;
const aspectRatio = 16 / 9;
@ -18,25 +23,57 @@ export const Video: Component<{ data: VideoProps }> = ({ data }) => {
}, [videoSize]);
return (
<Link href={url}>
<NextLink href={url}>
<Card>
<CardBody>
<div className="flex flex-row gap-4">
<Image
width={width}
height={height}
src={data.thumbnail}
alt={data.title}
as={NextImage}
unoptimized
className="aspect-video"
/>
<div className="relative">
<Image
width={width}
height={height}
src={data.thumbnail}
alt={data.title}
as={NextImage}
unoptimized
/>
<p className="text-small rounded-md z-10 absolute bottom-2 right-2 bg-content2 p-1">
{formatDuration(data.duration)}
</p>
{data.live && (
<p className="text-small rounded-md z-10 absolute bottom-2 left-2 bg-danger p-1">
LIVE
</p>
)}
</div>
<div className="flex flex-col gap-2">
<h1 className="text-xl">{data.title}</h1>
<div className="flex flex-row gap-4 items-center font-semibold text-default-600">
<h1>{formatViewCount(data.views)} views</h1>
<h1>{formatUploadedTime(data.uploaded)}</h1>
</div>
<Link
as={NextLink}
href={channelUrl}
className="flex flex-row gap-2 items-center"
>
{data.author.avatar && (
<Image
width={64}
height={64}
src={data.author.avatar}
alt={data.author.name}
as={NextImage}
unoptimized
/>
)}
<h1 className="text-lg text-default-600">{data.author.name}</h1>
</Link>
<p className="text-default-600">{data.description}</p>
</div>
</div>
</CardBody>
</Card>
</Link>
</NextLink>
);
};

@ -32,7 +32,11 @@ export default class Transformer {
title: data.title,
description: "",
live: false,
author: { id: channelId, name: data.uploaderName }
author: {
id: channelId,
name: data.uploaderName,
avatar: data.uploaderAvatar
}
};
}

@ -4,6 +4,7 @@ export interface Video {
author: {
name: string;
id: string;
avatar?: string;
};
thumbnail: string;
description: string;

@ -23,13 +23,16 @@ export const Search: Component<{ initialQueryValue?: string }> = ({
const { isLoading, error, data } = useQuery({
queryKey: ["search", "suggestions", searchQueryDebounced],
queryFn: () => client.getSearchSuggestions(searchQueryDebounced),
enabled: searchQueryDebounced.length !== 0
queryFn: () => {
if (searchQueryDebounced.length === 0) return [];
return client.getSearchSuggestions(searchQueryDebounced);
}
});
const handleSubmit = useCallback(() => {
router.push(`/results?search_query=${searchQuery}`);
}, [searchQuery]);
const submit = useCallback((query: string) => {
router.push(`/results?search_query=${query}`);
}, []);
const suggestions = useMemo(
() =>
@ -41,21 +44,26 @@ export const Search: Component<{ initialQueryValue?: string }> = ({
);
return (
<form onSubmit={handleSubmit}>
<form onSubmit={() => submit(searchQuery)}>
<Autocomplete
isClearable
name="search-bar"
name="search_query"
value={searchQuery}
isLoading={isLoading}
defaultInputValue={initialQueryValue}
onValueChange={setSearchQuery}
onKeyDown={(e) => {
if (e.key === "Enter") {
submit(searchQuery);
}
}}
startContent={<SearchIcon className="text-xl" />}
defaultItems={suggestions}
onSelectionChange={(key) => {
if (key === null) return;
setSearchQuery(key.toString());
handleSubmit();
submit(key.toString());
}}
errorMessage={error !== null ? error.toString() : ""}
isInvalid={error !== null}

@ -7,8 +7,8 @@ export const useClient = () => {
const [client] = useState(
() =>
new Client([
// { baseUrl: "https://invidious.drgns.space", type: ApiType.Invidious }
{ baseUrl: "https://pipedapi.kavin.rocks", type: ApiType.Piped }
{ baseUrl: "https://invidious.drgns.space", type: ApiType.Invidious }
// { baseUrl: "https://pipedapi.kavin.rocks", type: ApiType.Piped }
])
);

@ -2,7 +2,7 @@ import { Duration } from "luxon";
const formatDuration = (ms: number): string => {
if (ms / (60 * 60 * 1000) >= 1)
return Duration.fromMillis(ms).toFormat("HH:mm:ss");
return Duration.fromMillis(ms).toFormat("hh:mm:ss");
else return Duration.fromMillis(ms).toFormat("mm:ss");
};

Loading…
Cancel
Save