diff --git a/apps/dashboard/public/file.svg b/apps/dashboard/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/apps/dashboard/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dashboard/public/globe.svg b/apps/dashboard/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/apps/dashboard/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dashboard/public/next.svg b/apps/dashboard/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/apps/dashboard/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dashboard/public/vercel.svg b/apps/dashboard/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/apps/dashboard/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dashboard/public/window.svg b/apps/dashboard/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/apps/dashboard/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx index af78b78..ac66db5 100644 --- a/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx +++ b/apps/dashboard/src/app/[locale]/(app)/(sidebar)/articles/page.tsx @@ -1,20 +1,33 @@ import { Metadata } from "next"; import { ArticlesFeed } from "#dashboard/components/articles-feed"; +import { CategoriesCarousel } from "#dashboard/components/categories-carousel"; +import { PageHeader } from "#dashboard/components/shell/page-header"; import { PageLayout } from "#dashboard/components/shell/page-layout"; -import { HydrateClient, prefetch, trpc } from "#dashboard/trpc/server"; +import { HydrateClient, batchPrefetch, trpc } from "#dashboard/trpc/server"; export const metadata: Metadata = { title: "Articles | Basango Dashboard", }; export default function Page() { - prefetch(trpc.categories.list.queryOptions()); - prefetch(trpc.articles.list.infiniteQueryOptions({ limit: 12 })); + batchPrefetch([ + trpc.articles.list.infiniteQueryOptions({ limit: 12 }), + trpc.categories.list.queryOptions(), + ]); return ( - + + + + + } + headersNumber={2} + title="Articles" + > diff --git a/apps/dashboard/src/components/articles-feed.tsx b/apps/dashboard/src/components/articles-feed.tsx index 3308fa1..52c73ab 100644 --- a/apps/dashboard/src/components/articles-feed.tsx +++ b/apps/dashboard/src/components/articles-feed.tsx @@ -6,25 +6,19 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import { Loader2 } from "lucide-react"; import * as React from "react"; +import { useCategoryFilterParams } from "#dashboard/hooks/use-category-filter-params"; import { useTRPC } from "#dashboard/trpc/client"; import { ArticleCard, ArticleCardSkeleton } from "./article-card"; -import { CategoriesCarousel } from "./categories-carousel"; type ArticlesTableProps = { sourceId?: string; }; -const PLACEHOLDER_COUNT = 8; - export function ArticlesFeed({ sourceId }: ArticlesTableProps) { + const { selectedCategory } = useCategoryFilterParams(); + const trpc = useTRPC(); - const [selectedCategory, setSelectedCategory] = React.useState(null); - - const handleCategorySelect = React.useCallback((categoryId: string | null) => { - setSelectedCategory((current) => (current === categoryId ? null : categoryId)); - }, []); - const query = useInfiniteQuery( trpc.articles.list.infiniteQueryOptions( { @@ -48,8 +42,6 @@ export function ArticlesFeed({ sourceId }: ArticlesTableProps) { return (
- - {query.isError && ( Unable to load articles @@ -61,7 +53,7 @@ export function ArticlesFeed({ sourceId }: ArticlesTableProps) { {isInitialLoading ? (
- {Array.from({ length: PLACEHOLDER_COUNT }).map((_, index) => ( + {Array.from({ length: 8 }).map((_, index) => ( ))}
diff --git a/apps/dashboard/src/components/categories-carousel.tsx b/apps/dashboard/src/components/categories-carousel.tsx index b3653d7..5ddb0d4 100644 --- a/apps/dashboard/src/components/categories-carousel.tsx +++ b/apps/dashboard/src/components/categories-carousel.tsx @@ -1,34 +1,23 @@ "use client"; -import { - Carousel, - CarouselContent, - CarouselItem, - CarouselNext, - CarouselPrevious, -} from "@basango/ui/components/carousel"; +import { Carousel, CarouselContent, CarouselItem } from "@basango/ui/components/carousel"; import { Skeleton } from "@basango/ui/components/skeleton"; import { cn } from "@basango/ui/lib/utils"; import { useQuery } from "@tanstack/react-query"; import * as React from "react"; import { Show } from "#dashboard/components/shell/show"; +import { useCategoryFilterParams } from "#dashboard/hooks/use-category-filter-params"; import { useTRPC } from "#dashboard/trpc/client"; -type Props = { - onSelect: (categoryId: string | null) => void; - selectedCategory: string | null; -}; - -const PLACEHOLDER_COUNT = 10; - -export function CategoriesCarousel({ onSelect, selectedCategory }: Props) { +export function CategoriesCarousel() { + const { selectedCategory, setSelectedCategory } = useCategoryFilterParams(); const trpc = useTRPC(); const { data, isLoading } = useQuery(trpc.categories.list.queryOptions()); const categories = data ?? []; return ( -
+
- onSelect(null)}> + setSelectedCategory(null)}> All ( + fallback={Array.from({ length: 10 }).map((_, index) => ( ))} - when={isLoading && categories.length > 0} + when={!isLoading && data} > {categories.map((category) => ( onSelect(category.id)} + onClick={() => setSelectedCategory(category.id)} > {category.name} @@ -63,8 +52,6 @@ export function CategoriesCarousel({ onSelect, selectedCategory }: Props) { ))} - -
); diff --git a/apps/dashboard/src/components/shell/page-header.tsx b/apps/dashboard/src/components/shell/page-header.tsx index 5206cd6..49bc9b7 100644 --- a/apps/dashboard/src/components/shell/page-header.tsx +++ b/apps/dashboard/src/components/shell/page-header.tsx @@ -14,7 +14,7 @@ type Props = { export function PageHeader({ title }: Props) { return ( -
+
diff --git a/apps/dashboard/src/components/shell/page-layout.tsx b/apps/dashboard/src/components/shell/page-layout.tsx index fed1e03..2a087e8 100644 --- a/apps/dashboard/src/components/shell/page-layout.tsx +++ b/apps/dashboard/src/components/shell/page-layout.tsx @@ -1,3 +1,4 @@ +import { cn } from "@basango/ui/lib/utils"; import React from "react"; import { PageHeader } from "#dashboard/components/shell/page-header"; @@ -6,15 +7,42 @@ interface PageProps { children: React.ReactNode; title?: string | React.ReactNode; header?: React.ReactNode; + headersNumber?: 1 | 2; } +const isEmptyHeader = (header: React.ReactNode | undefined): boolean => { + if (!header) return true; + + if (React.isValidElement(header) && header.type === React.Fragment) { + const props = header.props as { children?: React.ReactNode }; + + if (!props.children) return true; + + if (Array.isArray(props.children) && props.children.length === 0) { + return true; + } + } + + return false; +}; + +const height = { + 1: "h-[calc(100svh-40px)] lg:h-[calc(100svh-56px)]", + 2: "h-[calc(100svh-80px)] lg:h-[calc(100svh-96px)]", +}; + export const PageLayout = (props: React.PropsWithChildren) => { - const { title, header = , children } = props; + const { title, header = , headersNumber = 1, children } = props; return (
{header} -
+
{children}
diff --git a/apps/dashboard/src/hooks/use-category-filter-params.ts b/apps/dashboard/src/hooks/use-category-filter-params.ts new file mode 100644 index 0000000..c94fbaa --- /dev/null +++ b/apps/dashboard/src/hooks/use-category-filter-params.ts @@ -0,0 +1,10 @@ +import { parseAsString, useQueryState } from "nuqs"; + +export function useCategoryFilterParams(paramKey = "category") { + const [selectedCategory, setSelectedCategory] = useQueryState(paramKey, parseAsString); + + return { + selectedCategory, + setSelectedCategory, + }; +}