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 Oct 27, 2023
2 parents 0af2330 + aa9e1ca commit 0cb16a0
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 39 deletions.
49 changes: 43 additions & 6 deletions packages/react/src/components/channel/ChannelCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ import { Button } from "@/shadcn/ui/button";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { ChannelMenu } from "./ChannelMenu";

interface ChannelCardProps extends Channel {}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type WithNonOptional<T, NonOptionalKeys extends keyof T> = Pick<
T,
NonOptionalKeys
> &
Partial<Omit<T, NonOptionalKeys>>;
type PartialChannel = WithNonOptional<
Channel,
keyof ShortChannel | "subscriber_count" | "video_count"
>;

interface ChannelCardProps extends PartialChannel {}

export function ChannelCard({
id,
name,
english_name,
org,
group,
lang,
type,
photo,
subscriber_count,
video_count,
Expand All @@ -26,10 +43,29 @@ export function ChannelCard({
() => 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="flex h-full min-h-[24rem] w-full flex-col items-center gap-2 rounded-md bg-base-3 p-4">
<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 ?? ""}
Expand Down Expand Up @@ -64,14 +100,15 @@ export function ChannelCard({
className="w-full"
variant={isInFavorite ? "outline" : "secondary"}
disabled={mutateLoading}
onClick={() =>
onClick={() => {
mutate([
{
op: isInFavorite ? "remove" : "add",
channel_id: id,
},
])
}
]);
console.log(isInFavorite);
}}
>
{isInFavorite ? (
<div className="i-heroicons:heart-solid" />
Expand Down
64 changes: 64 additions & 0 deletions packages/react/src/components/channel/ChannelMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/shadcn/ui/dropdown-menu";
import { useToast } from "@/shadcn/ui/use-toast";
import { blockedChannelsAtom } from "@/store/settings";
import { useAtom } from "jotai";
import { Children, ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import { useCopyToClipboard } from "usehooks-ts";

interface ChannelMenuProps extends ShortChannel {
children: ReactNode;
}

export function ChannelMenu({
children,
id: channelId,
...rest
}: ChannelMenuProps) {
const [blockedChannels, setBlockedChannels] = useAtom(blockedChannelsAtom);
const { toast } = useToast();
const [, copy] = useCopyToClipboard();
const { t } = useTranslation();

const isBlocked = blockedChannels.some(({ id }) => id === channelId);

return (
<DropdownMenu>
<DropdownMenuTrigger>{Children.only(children)}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
className="flex gap-2"
onClick={() => {
copy(`${window.location.origin}/channel/${channelId}`);
toast({
title: t("component.toast.copiedToClipboard"),
});
}}
>
<div className="i-heroicons:link" />
{t("component.videoCard.copyLink")}
</DropdownMenuItem>
<DropdownMenuItem
className="flex gap-2"
onClick={() =>
setBlockedChannels((currVal) =>
isBlocked
? currVal.filter(({ id }) => id !== channelId)
: [...currVal, { id: channelId, ...rest }],
)
}
>
<div className="i-heroicons:no-symbol" />
{isBlocked
? t("component.channelSocials.unblock")
: t("component.channelSocials.block")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
87 changes: 87 additions & 0 deletions packages/react/src/components/topic/TopicPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useState, useEffect } from "react";
import { ChevronsUpDown } from "lucide-react";
import { Button } from "@/shadcn/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/shadcn/ui/command";
import { Popover, PopoverTrigger, PopoverContent } from "@/shadcn/ui/popover";
import { useTranslation } from "react-i18next";
import { useAtom, useAtomValue } from "jotai/react";
import { useSearchAutoCompleteMutation } from "@/services/search.service";
import atomWithDebounce from "@/lib/atomWithDebounce";

const { debouncedValueAtom, currentValueAtom } = atomWithDebounce("", 300);

interface TopicPickerProps {
onSelect: (topicId: string) => void;
}

export function TopicPicker({ onSelect }: TopicPickerProps) {
const [deboundcedValue, setDebouncedValue] = useAtom(debouncedValueAtom);
const currentValue = useAtomValue(currentValueAtom);
const { t } = useTranslation();
const [open, setOpen] = useState(false);

const { data, isPending, mutate } = useSearchAutoCompleteMutation();

useEffect(() => {
mutate({
q: deboundcedValue,
t: "topic",
n: 10,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deboundcedValue]);

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
size="lg"
aria-expanded={open}
className="w-full justify-between px-4"
>
{t("component.topicPicker.pickLabel")}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="max-w-[80vw] p-0">
<Command>
<CommandInput
value={currentValue}
onValueChange={setDebouncedValue}
placeholder={t("component.topicPicker.searchLabel")}
/>
<CommandList>
<CommandEmpty>{t("component.topicPicker.notFound")}</CommandEmpty>
<CommandGroup>
{data?.topic?.map(({ id }) => (
<CommandItem
key={id}
onSelect={(topicId) => {
onSelect(topicId);
setOpen(false);
}}
>
{id}
</CommandItem>
))}
{isPending && (
<CommandItem className="flex justify-center py-2" disabled>
<div className="i-lucide:loader-2 animate-spin" />
</CommandItem>
)}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
16 changes: 12 additions & 4 deletions packages/react/src/locales/en/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ component:
addToFavorites: Add to Favorites
removeFromFavorites: Remove from Favorites
unblock: Unblock this channel
block: Block this channel's content across Holodex
block: Block channel
blocked: Blocked
mainNav:
home: Home
Expand Down Expand Up @@ -212,6 +212,10 @@ component:
pickDate: Pick a date...
toast:
copiedToClipboard: Copied to clipboard
topicPicker:
searchLabel: Search topics...
notFound: No topic found
pickLabel: Pick topics...
views:
channel:
video: Videos
Expand Down Expand Up @@ -323,9 +327,9 @@ views:
gridSizeLabel: Thumbnail/Grid Size
gridSizeMsg: Change thumbnail/video grid size on Home/Favorites page
gridSize:
- Large
- Medium
- Small
- Grid
- List
- Dense List
hideCollabStreamsLabel: Hide Collab Streams
hideCollabStreamsMsg: Hide collab streams from your favorites feed
hidePlaceholderStreams: Hide Placeholder Streams
Expand All @@ -339,6 +343,10 @@ views:
languageSearch: Search language...
languageNotfound: No language found
userNotLinked: Not linked
defaultHomepage:
lastVisitedOrgHome: Last Visited Org Home
favoritesWhenLoggedIn: Favorites (When Logged In)
hideFeaturesLabel: Hide Features
app:
update_available: An update is available
update_btn: Update
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/routes/favourites.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useFavorites } from "@/services/user.service";
import { ChannelCard } from "@/components/channel/ChannelCard";
import { VirtuosoGrid } from "react-virtuoso";

export default function Favorites() {
const { data: favChannels } = useFavorites();
return (
<>
<div className="h-full w-full p-4 md:p-8">
<VirtuosoGrid
useWindowScroll
listClassName="w-full grid grid-cols-[repeat(auto-fill,_minmax(240px,_1fr))] gap-x-4 gap-y-6"
data={favChannels?.flat() ?? []}
itemContent={(_, channel) => <ChannelCard {...channel} />}
/>
</div>
</>
);
}
13 changes: 12 additions & 1 deletion packages/react/src/routes/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const Settings = React.lazy(() => import("./settings"));
const SettingsLang = React.lazy(() => import("./settings/lang"));
const SettingsTheme = React.lazy(() => import("./settings/theme"));
const SettingsUser = React.lazy(() => import("./settings/user"));
const SettingsHomepage = React.lazy(() => import("./settings/homepage"));
const SettingsBlocked = React.lazy(() => import("./settings/blocked"));
const About = React.lazy(() => import("./about"));
const AboutGeneral = React.lazy(() => import("./about/general"));
const AboutChangelog = React.lazy(() => import("./about/changelog"));
Expand All @@ -23,6 +25,7 @@ const AboutPrivacy = React.lazy(() => import("./about/privacy"));
const ChannelsOrg = React.lazy(() => import("./channelsOrg"));
const Channel = React.lazy(() => import("./channel"));
const Kitchensink = React.lazy(() => import("@/Kitchensink"));
const Favourites = React.lazy(() => import("./favourites"));

const store = getDefaultStore();

Expand All @@ -33,7 +36,7 @@ const router = createBrowserRouter([
children: [
{
path: "favorites",
element: <div>Favorites</div>,
element: <Favourites />,
},
{
path: "search",
Expand Down Expand Up @@ -97,6 +100,14 @@ const router = createBrowserRouter([
path: "user",
element: <SettingsUser />,
},
{
path: "homepage",
element: <SettingsHomepage />,
},
{
path: "blocked",
element: <SettingsBlocked />,
},
// Add children routes similar to above pattern
],
},
Expand Down
8 changes: 8 additions & 0 deletions packages/react/src/routes/settings/blocked.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { blockedChannelsAtom } from "@/store/settings";
import { useAtom } from "jotai";

export default function SettingsBlocked() {
const [blockedChannels, setBlockedChannels] = useAtom(blockedChannelsAtom);

return <div className="flex flex-col gap-2"></div>;
}
Loading

0 comments on commit 0cb16a0

Please sign in to comment.