-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
253 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
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 { Link } from "react-router-dom"; | ||
|
||
interface ChannelCardProps extends Channel {} | ||
|
||
export function ChannelCard({ | ||
id, | ||
name, | ||
photo, | ||
subscriber_count, | ||
video_count, | ||
clip_count, | ||
top_topics, | ||
twitter, | ||
twitch, | ||
}: ChannelCardProps) { | ||
const { mutate, isLoading: mutateLoading } = useFavoriteMutation(); | ||
const { data } = useFavorites(); | ||
const isInFavorite = useMemo( | ||
() => data?.some((channel) => id === channel.id), | ||
[data], | ||
); | ||
|
||
return ( | ||
// Set min-height because react-virtuoso will break if the height is not fixed | ||
<div className="w-full h-full min-h-[24rem] flex flex-col gap-2 items-center p-4 bg-base-3 rounded-md"> | ||
<img className="w-24 h-24 rounded-full" src={photo ?? ""} /> | ||
<div className="font-xl font-bold text-center line-clamp-2">{name}</div> | ||
<div className="flex flex-col items-center"> | ||
<div className="text-sm text-base-11 whitespace-nowrap"> | ||
{formatCount(subscriber_count ?? 0)} subscribers | ||
</div> | ||
<div className="flex flex-wrap justify-center gap-x-1 gap-y-0 text-sm text-base-11"> | ||
<span className="whitespace-nowrap">{video_count ?? "0"} videos</span> | ||
<span>/</span> | ||
<span className="whitespace-nowrap">{clip_count ?? "0"} clips</span> | ||
</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, | ||
}, | ||
]) | ||
} | ||
> | ||
{isInFavorite ? ( | ||
<div className="i-heroicons:heart-solid" /> | ||
) : ( | ||
<div className="i-heroicons:heart" /> | ||
)} | ||
{isInFavorite ? "Unfavorite" : "Favorite"} | ||
</Button> | ||
<div className="w-full flex gap-2"> | ||
<Button asChild className="w-full" variant="ghost" size="icon-lg"> | ||
<Link to={`https://www.youtube.com/channel/${id}`} target="_blank"> | ||
<div className="i-lucide: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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { ChannelCard } from "@/components/channel/ChannelCard"; | ||
import { useChannels } from "@/services/channel.service"; | ||
import { useParams, useSearchParams } from "react-router-dom"; | ||
import { VirtuosoGrid } from "react-virtuoso"; | ||
|
||
export function ChannelsOrg() { | ||
const { org } = useParams(); | ||
// const [org, setOrg] = useAtom(orgAtom); | ||
|
||
const { data: channels, fetchNextPage: fetchChannels } = useChannels({ org, sort: 'suborg' }); | ||
|
||
return ( | ||
<div className="w-full h-full p-4 md:p-8"> | ||
<VirtuosoGrid | ||
useWindowScroll | ||
listClassName="w-full grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 gap-x-4 gap-y-6" | ||
data={channels?.pages.flat() ?? []} | ||
itemContent={(_, channel) => <ChannelCard {...channel} />} | ||
endReached={async () => { | ||
await fetchChannels(); | ||
}} | ||
/> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { useClient } from "@/hooks/useClient"; | ||
import { | ||
UseInfiniteQueryOptions, | ||
UseQueryOptions, | ||
useInfiniteQuery, | ||
useQuery, | ||
} from "@tanstack/react-query"; | ||
import { AxiosError } from "axios"; | ||
|
||
interface UseChannelsParams { | ||
limit?: number; | ||
offset?: number; | ||
sort?: string; | ||
order?: "asc" | "desc"; | ||
type?: ChannelType; | ||
org?: string; | ||
suborg?: string; | ||
lang?: string; | ||
} | ||
|
||
export function useChannels( | ||
params: UseChannelsParams, | ||
config?: UseInfiniteQueryOptions<Channel[], AxiosError>, | ||
) { | ||
const client = useClient(); | ||
|
||
return useInfiniteQuery<Channel[], AxiosError>({ | ||
queryKey: ["channels", params], | ||
queryFn: async ({ pageParam = 0 }) => | ||
( | ||
await client<Channel[]>("/channels", { | ||
params: { ...params, offset: pageParam }, | ||
}) | ||
).data, | ||
getNextPageParam: (lastPage, allPages) => | ||
lastPage.length ? allPages.flat().length : undefined, | ||
...config, | ||
}); | ||
} | ||
|
||
export function useChannel( | ||
channelId: string, | ||
config?: UseQueryOptions<Channel, AxiosError>, | ||
) { | ||
const client = useClient(); | ||
|
||
return useQuery<Channel, AxiosError>( | ||
["channel", channelId], | ||
async () => (await client<Channel>(`/channels/${channelId}`)).data, | ||
config, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { useClient } from "@/hooks/useClient"; | ||
import { | ||
UseMutationOptions, | ||
UseQueryOptions, | ||
useMutation, | ||
useQuery, | ||
useQueryClient, | ||
} from "@tanstack/react-query"; | ||
import { AxiosError } from "axios"; | ||
|
||
export function useFavorites( | ||
config?: UseQueryOptions<FavoriteChannel[], AxiosError>, | ||
) { | ||
const client = useClient(); | ||
|
||
return useQuery<FavoriteChannel[], AxiosError>( | ||
["user", "favorites"], | ||
async () => (await client<FavoriteChannel[]>("/users/favorites")).data, | ||
config, | ||
); | ||
} | ||
|
||
interface FavoriteMutationPayload { | ||
op: "add" | "remove"; | ||
channel_id: string; | ||
} | ||
|
||
export function useFavoriteMutation( | ||
config?: UseMutationOptions< | ||
FavoriteChannel[], | ||
AxiosError, | ||
FavoriteMutationPayload[] | ||
>, | ||
) { | ||
const queryClient = useQueryClient(); | ||
const client = useClient(); | ||
|
||
return useMutation<FavoriteChannel[], AxiosError, FavoriteMutationPayload[]>( | ||
async (payload) => | ||
( | ||
await client<FavoriteChannel[]>("/users/favorites", { | ||
method: "PATCH", | ||
data: payload, | ||
}) | ||
).data, | ||
{ | ||
...config, | ||
onSuccess: (res, ...args) => { | ||
queryClient.setQueryData(["user", "favorites"], res); | ||
if (config?.onSuccess) config?.onSuccess(res, ...args); | ||
}, | ||
}, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { atomWithStorage } from "jotai/utils"; | ||
|
||
export const orgAtom = atomWithStorage('org', 'Hololive'); |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters