Skip to content

Commit

Permalink
Merge pull request #131 from thaian1234/ft/category
Browse files Browse the repository at this point in the history
Ft/category
  • Loading branch information
thaian1234 authored Dec 5, 2024
2 parents edf1a65 + 62eb8bd commit 835240c
Show file tree
Hide file tree
Showing 31 changed files with 477 additions and 470 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ yarn-error.log*
# local env files
.env*.local
.env
.env.production

# vercel
.vercel
Expand Down
3 changes: 1 addition & 2 deletions app/(main)/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React from "react";
import { CategoryList } from "@/lib/features/category/components/category-list";
import { FollowingStream } from "@/lib/features/stream/components/preview/following-stream";
import { RecommendStream } from "@/lib/features/stream/components/preview/recommend-stream";
import { StreamSectionLayout } from "@/lib/features/stream/layouts/stream-section.layout";

export default function HomePage() {
return (
Expand All @@ -12,7 +11,7 @@ export default function HomePage() {
Welcome to Your Dashboard
</h2>
<RecommendStream />
<CategoryList />
{/* <CategoryList /> */}
<FollowingStream />
</section>
);
Expand Down
99 changes: 55 additions & 44 deletions app/(main)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import { Loader2 } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useQueryState } from "nuqs";
import { useState } from "react";

import { UserPreview } from "@/lib/components/profile/search/user-preview";
import { useCategoryTree } from "@/lib/features/category/hooks/use-category-tree";
import { searchApi } from "@/lib/features/search/apis";
import { LivesPreview } from "@/lib/features/search/components/live-preview";

import { CategoryFilter } from "@/components/category-filter";
import {
Pagination,
PaginationContent,
Expand Down Expand Up @@ -128,63 +131,71 @@ export default function SearchPage() {
});
const searchParams = useSearchParams();
const searchQuery = searchParams.get("searchTerm");
const { data, error, isPending } = searchApi.query.useSearch(
currentPage,
"10",
searchQuery || "",
);
const { data, error, isPending } = searchApi.query.useSearch({
filterBy: searchQuery,
page: parseInt(currentPage),
size: 4,
});
const tabs = ["All", "Live", "Channel"];

if (data === undefined || isPending) return <Loader2 />;

if (error) return <p>Something went wrong</p>;
const streams = data.data.data.streams;

const handleChangePage = (page: number) => {
setCurrentPage(page.toString());
};

return (
<Tabs defaultValue="All" className="w-full px-10">
<TabsList className="mb-8 grid w-[300px] grid-cols-3 space-x-4 bg-black-1">
{tabs.map((tabName, index) => (
<TabsTrigger
className="w-auto rounded-full bg-search text-white data-[state=active]:bg-teal-2"
value={tabName}
key={index}
>
{tabName}
</TabsTrigger>
))}
</TabsList>
<TabsContent value="All">
<div className="space-y-4">
<p className="text-2xl">Channel</p>
<div className="flex max-w-[700px] flex-col">
<UserPreview users={data.data.data.users} limit={4} />
<section className="relative">
<div className="absolute right-1/4 top-0 flex min-w-72 items-center justify-center rounded-lg border border-slate-500 p-4">
<CategoryFilter />
</div>
<Tabs defaultValue="All" className="w-full px-10">
<TabsList className="mb-8 grid w-[300px] grid-cols-3 space-x-4 bg-black-1">
{tabs.map((tabName, index) => (
<TabsTrigger
className="w-auto rounded-full bg-search text-white data-[state=active]:bg-teal-2"
value={tabName}
key={index}
>
{tabName}
</TabsTrigger>
))}
</TabsList>
<TabsContent value="All">
<div className="space-y-4">
<p className="text-2xl">User</p>
<div className="flex max-w-[700px] flex-col">
<UserPreview
users={data.data.data.users}
limit={4}
/>
</div>

<p className="text-2xl">Channel</p>
<div className="flex flex-col space-y-4">
<LivesPreview streams={streams} />
</div>
</div>
</TabsContent>

<p className="text-2xl">Live</p>
<TabsContent value="User">
<div className="flex flex-col space-y-4">
<LivesPreview streams={data.data.data.streams} />
<LivesPreview streams={streams} />
</div>
</TabsContent>
<TabsContent value="Channel">
<div className="flex max-w-[700px] flex-col">
<UserPreview users={data.data.data.users} />
</div>
</div>
</TabsContent>

<TabsContent value="Live">
<div className="flex flex-col space-y-4">
<LivesPreview streams={data.data.data.streams} />
</div>
</TabsContent>
<TabsContent value="Channel">
<div className="flex max-w-[700px] flex-col">
<UserPreview users={data.data.data.users} />
</div>
</TabsContent>
<PaginationComponent
currentPage={data.data.pagination.currentPage}
totalPages={data.data.pagination.totalPages}
onPageChange={handleChangePage}
/>
</Tabs>
</TabsContent>
<PaginationComponent
currentPage={data.data.pagination.currentPage}
totalPages={data.data.pagination.totalPages}
onPageChange={handleChangePage}
/>
</Tabs>
</section>
);
}
Binary file modified bun.lockb
Binary file not shown.
90 changes: 90 additions & 0 deletions components/category-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client";

import * as React from "react";

import { categoryApi } from "@/lib/features/category/apis";
import { useCategoryTree } from "@/lib/features/category/hooks/use-category-tree";

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";

import { Spinner } from "./ui/spinner";

export function CategoryFilter() {
const { data: categories, isPending } = categoryApi.query.useGetDetail({
page: "1",
});
const { expandedIds, selectedIds, handleSelect, handleExpand } =
useCategoryTree();

if (isPending) return <Spinner />;
if (!categories) return <div>No categories found</div>;

return (
<Accordion type="single" className="w-full max-w-sm">
<AccordionItem value="categories">
<AccordionTrigger className="text-lg font-semibold">
Categories
</AccordionTrigger>
<AccordionContent>
<div className="space-y-4">
{categories.data.categories.map((category) => (
<div key={category.id} className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
id={category.id}
checked={selectedIds.includes(
category.id,
)}
onCheckedChange={() =>
handleSelect(category.id)
}
/>
<Label
htmlFor={category.id}
className="text-sm font-medium"
>
{category.name}
</Label>
</div>
<div className="ml-6 space-y-2">
{category.children &&
category.children.map((subcategory) => (
<div
key={subcategory.id}
className="flex items-center space-x-2"
>
<Checkbox
id={subcategory.id}
checked={selectedIds.includes(
subcategory.id,
)}
onCheckedChange={() =>
handleSelect(
subcategory.id,
)
}
/>
<Label
htmlFor={subcategory.id}
className="text-sm font-normal leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{subcategory.name}
</Label>
</div>
))}
</div>
</div>
))}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
);
}
1 change: 1 addition & 0 deletions components/thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function VideoThumbnail({
alt={"thumbnail"}
objectFit="object-cover"
fill
className="overflow-hidden rounded-lg"
onError={() => setThumbnailError(true)}
/>
)}
Expand Down
58 changes: 58 additions & 0 deletions components/ui/accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client"

import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"

import { cn } from "@/lib/utils"

const Accordion = AccordionPrimitive.Root

const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"

const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName

const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))

AccordionContent.displayName = AccordionPrimitive.Content.displayName

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
43 changes: 19 additions & 24 deletions lib/components/profile/search/user-preview-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Users } from "lucide-react";
import { useRouter } from "next/navigation";

import { ROUTES } from "@/lib/configs/routes.config";
import { FollowButton } from "@/lib/features/follow/components/follow-button";
import { useAuth } from "@/lib/providers/auth-provider";

Expand All @@ -12,7 +13,7 @@ interface UserPreviewCardProps {
id: string;
username: string;
followers: number;
imageUrl: string;
imageUrl: string | null;
isLive: boolean;
isFollow: boolean;
}
Expand All @@ -22,46 +23,40 @@ export function UserPreviewCard({
followers,
imageUrl,
isLive,
isFollow
isFollow,
}: UserPreviewCardProps) {
const router = useRouter();
const { user } = useAuth();
if (!user) return;

const handleNavigateLive = () => {
router.push(ROUTES.STREAM_PAGE(username));
};
return (
<Card className="w-full overflow-hidden py-4 transition-all duration-300 ease-in-out hover:bg-accent/50 hover:shadow-lg">
<CardContent className="px-6">
<div className="flex items-center justify-between">
<div className="flex flex-grow items-center space-x-6">
<div className="flex cursor-pointer items-center justify-between">
<div
className="flex flex-grow items-center space-x-6"
onClick={handleNavigateLive}
>
<UserAvatar imageUrl={imageUrl} size="xl" />

<div className="min-w-0 flex-grow">
<div className="mb-2 flex items-center space-x-2">
<h3 className="line-clamp-1 text-lg font-semibold transition-colors duration-300 ease-in-out hover:text-primary">
<a
href={`/dashboard/${username}`}
className="hover:underline"
>
{username}
</a>
<h3 className="line-clamp-1 text-lg font-semibold transition-colors duration-300 ease-in-out hover:text-primary hover:underline">
{username}
</h3>
{(isLive && <Badge className="bg-red-500 hover:bg-red-500 text-white">
LIVE
</Badge>)}
{isLive && (
<Badge className="bg-red-500 text-white hover:bg-red-500">
LIVE
</Badge>
)}
</div>
<p className="mb-1 line-clamp-1 text-sm text-muted-foreground">
Category
</p>
<p className="mb-2 line-clamp-1 text-sm text-muted-foreground">
Category
</p>
<div className="flex items-center text-sm text-muted-foreground">
<Users className="mr-1 h-4 w-4" />
<span>{followers} Followers</span>
</div>
</div>
</div>
<FollowButton followingId={user.id} isFollowed={isFollow} />
<FollowButton followingId={id} isFollowed={isFollow} />
</div>
</CardContent>
</Card>
Expand Down
Loading

0 comments on commit 835240c

Please sign in to comment.