Skip to content

Commit

Permalink
Merge branch 'next' of https://github.com/HolodexNet/Holodex into next
Browse files Browse the repository at this point in the history
  • Loading branch information
sphinxrave committed Nov 23, 2023
2 parents 94386f3 + c08f0f4 commit bb865cb
Show file tree
Hide file tree
Showing 32 changed files with 680 additions and 308 deletions.
28 changes: 15 additions & 13 deletions packages/react/src/components/about/request/DeleteChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Textarea } from "@/shadcn/ui/textarea";
import { useToast } from "@/shadcn/ui/use-toast";
import { SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { ChannelPicker } from "./ChannelPicker";
import { ChannelPicker } from "../../channel/ChannelPicker";

const formValues = {
channel: {
Expand Down Expand Up @@ -122,18 +122,20 @@ export function DeleteChannelForm() {
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Channel</FormLabel>
<ChannelPicker
name="channel.name"
form={form}
field={field}
onSelect={({ name, id }) => {
form.setValue("channel.name", name);
form.setValue(
"channel.link",
`https://www.youtube.com/channel/${id}`,
);
}}
/>
<FormControl>
<ChannelPicker
name="channel.name"
form={form}
value={field.value}
onSelect={({ name, id }) => {
form.setValue("channel.name", name);
form.setValue(
"channel.link",
`https://www.youtube.com/channel/${id}`,
);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
Expand Down
28 changes: 15 additions & 13 deletions packages/react/src/components/about/request/ModifyInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Textarea } from "@/shadcn/ui/textarea";
import { useToast } from "@/shadcn/ui/use-toast";
import { SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { ChannelPicker } from "./ChannelPicker";
import { ChannelPicker } from "../../channel/ChannelPicker";
import { LanguagePicker } from "./LanguagePicker";

const formValues = {
Expand Down Expand Up @@ -134,18 +134,20 @@ export function ModifyInfoForm() {
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Channel</FormLabel>
<ChannelPicker
form={form}
field={field}
name="channel.name"
onSelect={({ name, id }) => {
form.setValue("channel.name", name);
form.setValue(
"channel.link",
`https://www.youtube.com/channel/${id}`,
);
}}
/>
<FormControl>
<ChannelPicker
form={form}
value={field.value}
name="channel.name"
onSelect={({ name, id }) => {
form.setValue("channel.name", name);
form.setValue(
"channel.link",
`https://www.youtube.com/channel/${id}`,
);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
Expand Down
235 changes: 120 additions & 115 deletions packages/react/src/components/channel/ChannelCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { formatCount } from "@/lib/time";
import { useFavoriteMutation, useFavorites } from "@/services/user.service";
import { Badge } from "@/shadcn/ui/badge";
import { Button } from "@/shadcn/ui/button";
import { useMemo } from "react";
import { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { ChannelMenu } from "./ChannelMenu";
import { ChannelImg } from "./ChannelImg";
import { TopicBadge } from "../topic/TopicBadge";
import { ChannelSocials } from "./ChannelSocials";

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type WithNonOptional<T, NonOptionalKeys extends keyof T> = Pick<
T,
NonOptionalKeys
Expand All @@ -18,9 +17,20 @@ type PartialChannel = WithNonOptional<
keyof ShortChannel | "subscriber_count" | "video_count"
>;

interface ChannelCardProps extends PartialChannel {}
interface ChannelCardProps extends PartialChannel {
size: "xs" | "sm" | "md" | "lg";
showSubscribers?: boolean;
showVideoCount?: boolean;
showClipCount?: boolean;
children?: ReactNode;
}

export function ChannelCard({
size,
showSubscribers = true,
showVideoCount,
showClipCount,
children,
id,
name,
english_name,
Expand All @@ -37,115 +47,110 @@ export function ChannelCard({
twitch, // inactive,
}: ChannelCardProps) {
const { t } = useTranslation();
const { mutate, isPending: mutateLoading } = useFavoriteMutation();
const { data } = useFavorites();
const isInFavorite = useMemo(
() => data?.some((channel) => id === channel.id),
[data, id],
);
return (
// Set min-height because react-virtuoso will break if the height is not fixed
<div className="group relative flex h-full min-h-[24rem] w-full flex-col items-center gap-2 rounded-md bg-base-3 p-4">
<ChannelMenu
{...{
id,
name,
type,
english_name,
org,
group,
lang,
photo,
}}
>
<Button
size="icon-lg"
variant="ghost"
className="absolute right-4 top-4 hidden rounded-full group-hover:flex"
>
<div className="i-heroicons:ellipsis-vertical" />
</Button>
</ChannelMenu>
<img
className="-z-0 -mb-36 mt-4 h-32 w-32 rounded-full opacity-20 blur-2xl saturate-150"
src={photo ?? ""}
/>
<img className="z-10 h-24 w-24 rounded-full" src={photo ?? ""} />
<div className="z-10 line-clamp-2 text-center text-lg font-bold">
{name}
</div>
<div className="flex flex-col items-center">
<div className="whitespace-nowrap text-sm text-base-11">
{t("component.channelInfo.subscriberCount", {
n: formatCount(subscriber_count ?? "0"),
})}

switch (size) {
case "xs":
case "sm":
return (
<div className="flex items-center gap-4 rounded-lg bg-base-3 p-4">
<ChannelImg className="h-12 w-12" channelId={id} />
<div className="flex flex-col">
<div className="text-xs text-base-11">
{org}
{group && ` / ${group}`}
</div>
<div className="line-clamp-1 text-lg font-bold">{name}</div>
<div className="text-sm text-base-11">
{showSubscribers &&
t("component.channelInfo.subscriberCount", {
n: formatCount(subscriber_count ?? "0"),
})}
{showVideoCount &&
` / ${t("component.channelInfo.videoCount", {
0: video_count ?? "0",
})}`}
{showClipCount &&
` / ${t("component.channelInfo.clipCount", {
n: clip_count ?? "0",
})}`}
</div>
{size === "sm" && (
<div className="flex gap-1">
{top_topics?.map((topic) => <TopicBadge topic={topic} />)}
</div>
)}
</div>
<div className="grow" />
{children ?? (
<ChannelSocials
size="sm"
id={id}
twitter={twitter}
twitch={twitch}
/>
)}
</div>
<div className="flex flex-wrap justify-center gap-x-1 gap-y-0 text-sm text-base-11">
<span className="whitespace-nowrap">
{t("component.channelInfo.videoCount", { 0: video_count ?? 0 })}
</span>
<span>/</span>
<span className="whitespace-nowrap">
{t("component.channelInfo.clipCount", { n: clip_count ?? "0" })}
</span>
);

case "lg":
return (
// Set min-height because react-virtuoso will break if the height is not fixed
<div className="group relative flex h-full min-h-[24rem] w-full flex-col items-center gap-2 rounded-md bg-base-3 p-4">
<ChannelMenu
{...{
id,
name,
type,
english_name,
org,
group,
lang,
photo,
}}
>
<Button
size="icon-lg"
variant="ghost"
className="absolute right-4 top-4 hidden rounded-full group-hover:flex"
>
<div className="i-heroicons:ellipsis-vertical" />
</Button>
</ChannelMenu>
<ChannelImg
className="-z-0 -mb-36 mt-4 h-32 w-32 opacity-20 blur-2xl saturate-150"
channelId={id}
/>
<ChannelImg className="z-10 h-24 w-24" channelId={id} />
<div className="z-10 line-clamp-2 text-center text-lg font-bold">
{name}
</div>
<div className="flex flex-col items-center">
<div className="whitespace-nowrap text-sm text-base-11">
{t("component.channelInfo.subscriberCount", {
n: formatCount(subscriber_count ?? "0"),
})}
</div>
<div className="flex flex-wrap justify-center gap-x-1 gap-y-0 text-sm text-base-11">
<span className="whitespace-nowrap">
{t("component.channelInfo.videoCount", { 0: video_count ?? 0 })}
</span>
<span>/</span>
<span className="whitespace-nowrap">
{t("component.channelInfo.clipCount", { n: clip_count ?? "0" })}
</span>
</div>
</div>
{top_topics && <TopicBadge topic={top_topics[0]} />}
<div className="flex grow" />
{children ?? (
<ChannelSocials
id={id}
twitter={twitter}
twitch={twitch}
size="lg"
/>
)}
</div>
</div>
{top_topics && (
<Badge variant="outline" className="capitalize">
{top_topics[0]}
</Badge>
)}
<div className="flex grow" />
<Button
className="w-full"
variant={isInFavorite ? "outline" : "secondary"}
disabled={mutateLoading}
onClick={() => {
mutate([
{
op: isInFavorite ? "remove" : "add",
channel_id: id,
},
]);
console.log(isInFavorite);
}}
>
{isInFavorite ? (
<div className="i-heroicons:heart-solid" />
) : (
<div className="i-heroicons:heart" />
)}
{isInFavorite
? t("component.channelSocials.removeFromFavorites")
: t("component.channelSocials.addToFavorites")}
</Button>
<div className="flex w-full gap-2">
<Button
asChild
className="w-full text-[#282828] dark:text-white"
variant="ghost"
size="icon-lg"
>
{/* Youtube Logo needs to conform with YT guidelines https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors */}
<Link to={`https://www.youtube.com/channel/${id}`} target="_blank">
<div className="i-mdi:youtube" />
</Link>
</Button>
{twitter && (
<Button asChild className="w-full" variant="ghost" size="icon-lg">
<Link to={`https://x.com/${twitter}`} target="_blank">
<div className="i-lucide:twitter" />
</Link>
</Button>
)}
{twitch && (
<Button asChild className="w-full" variant="ghost" size="icon-lg">
<Link to={`https://twitch.tv/${twitch}`} target="_blank">
<div className="i-lucide:twitch" />
</Link>
</Button>
)}
</div>
</div>
);
);
}
}
19 changes: 19 additions & 0 deletions packages/react/src/components/channel/ChannelImg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { cn, getChannelPhoto } from "@/lib/utils";

export function ChannelImg({
channelId,
size = 100,
className,
}: {
channelId: string;
size?: number;
className?: string;
}) {
return (
<img
loading="lazy"
className={cn("rounded-full shrink-0", className)}
src={getChannelPhoto(channelId, size)}
/>
);
}
Loading

0 comments on commit bb865cb

Please sign in to comment.