diff --git a/frontend/app/(authenticated)/categories/[id]/page.tsx b/frontend/app/(authenticated)/categories/[id]/page.tsx new file mode 100644 index 00000000..7729c748 --- /dev/null +++ b/frontend/app/(authenticated)/categories/[id]/page.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; + +import { CategoryDTO, MiniEventDTO } from "@/client"; +import ArticleLoading from "@/components/news/article-loading"; +import NewsArticle from "@/components/news/news-article"; +import { getCategories } from "@/queries/category"; +import { getEventsForCategory } from "@/queries/event"; + +const Page = ({ params }: { params: { id: string } }) => { + const categoryId = parseInt(params.id); + const [categoryName, setCategoryName] = useState(""); + const { data: events, isSuccess: isEventsLoaded } = useQuery( + getEventsForCategory(categoryId), + ); + const { data: categories, isSuccess: isCategoriesLoaded } = + useQuery(getCategories()); + + // Very inefficient, but is there a better way to do this? New StoreProvider for CategoryDTO[]? + useEffect(() => { + if (isCategoriesLoaded && categories!.length > 0) { + categories!.forEach((category: CategoryDTO) => { + if (category.id == categoryId) { + setCategoryName(category.name); + } + }); + } + }, [categories, isCategoriesLoaded, categoryId]); + + const Articles = () => { + if (!isEventsLoaded) { + return ( + <> + + + + + ); + } + + const eventData = events!.data; + if (eventData.length == 0) { + return ( +
+

+ No recent events. Try refreshing the page. +

+
+ ); + } + + return eventData.map((newsEvent: MiniEventDTO, index: number) => ( + + )); + }; + + return ( +
+
+ + {new Date().toDateString()} + +

+ Top events from {categoryName} +

+
+ +
+ +
+
+ ); +}; + +export default Page; diff --git a/frontend/client/core/utils.ts b/frontend/client/core/utils.ts index f7103fc4..7d09decf 100644 --- a/frontend/client/core/utils.ts +++ b/frontend/client/core/utils.ts @@ -331,6 +331,10 @@ export const formDataBodySerializer = { }, }; +export const categoryIdsSerializer = (params: Record) => { + return params.category_ids?.map((categoryId: number) => `category_ids=${categoryId}`).join("&"); +}; + export const jsonBodySerializer = { bodySerializer: (body: T) => JSON.stringify(body), }; diff --git a/frontend/client/services.gen.ts b/frontend/client/services.gen.ts index 6e744ce4..e5a604e0 100644 --- a/frontend/client/services.gen.ts +++ b/frontend/client/services.gen.ts @@ -6,6 +6,7 @@ import { type Options, urlSearchParamsBodySerializer, } from "./client"; +import { categoryIdsSerializer } from "./core/utils"; import type { AskGpQuestionUserQuestionsAskGpQuestionGetData, AskGpQuestionUserQuestionsAskGpQuestionGetError, @@ -286,6 +287,7 @@ export const getEventsEventsGet = ( ThrowOnError >({ ...options, + paramsSerializer: categoryIdsSerializer, url: "/events/", }); }; diff --git a/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx b/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx index ab7d614c..043e67d8 100644 --- a/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx +++ b/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx @@ -1,13 +1,26 @@ +import { useRouter } from "next/navigation"; import { LucideIcon } from "lucide-react"; interface SidebarItemWithIconProps { Icon: LucideIcon; label: string; + categoryId: number; } -const SidebarItemWithIcon = ({ Icon, label }: SidebarItemWithIconProps) => { +const SidebarItemWithIcon = ({ + Icon, + label, + categoryId, +}: SidebarItemWithIconProps) => { + const router = useRouter(); + + const onClickCategory = () => router.push(`/categories/${categoryId}`); + return ( -
+
{ + const { data: categories } = useQuery(getCategories()); + return (

Other topics

- {otherTopics.map((category) => { - const categoryLabel = categoriesToDisplayName[category]; - const categoryIcon = categoriesToIconsMap[category]; + {categories?.map((category) => { + const categoryIcon = getIconFor(category.name); return ( ); })} diff --git a/frontend/components/news/news-article.tsx b/frontend/components/news/news-article.tsx index 0210e774..7ac00e34 100644 --- a/frontend/components/news/news-article.tsx +++ b/frontend/components/news/news-article.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useEffect, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/navigation"; diff --git a/frontend/queries/event.ts b/frontend/queries/event.ts index d8446897..03530fcb 100644 --- a/frontend/queries/event.ts +++ b/frontend/queries/event.ts @@ -2,7 +2,7 @@ import { queryOptions } from "@tanstack/react-query"; import { getEventEventsIdGet, - getUserAuthSessionGet, + getEventsEventsGet, } from "@/client/services.gen"; import { QueryKeys } from "./utils/query-keys"; @@ -16,3 +16,16 @@ export const getEvent = (id: number) => query: { id }, }).then((data) => data.data), }); + +export const getEventsForCategory = (categoryId: number) => + queryOptions({ + queryKey: [QueryKeys.Categories, categoryId], + queryFn: () => + getEventsEventsGet({ + withCredentials: true, + query: { + category_ids: [ categoryId ], + }, + }).then((data) => data.data), + }); + \ No newline at end of file diff --git a/frontend/types/categories.ts b/frontend/types/categories.ts index bc0e5679..e455628e 100644 --- a/frontend/types/categories.ts +++ b/frontend/types/categories.ts @@ -31,7 +31,7 @@ export enum Category { export const getCategoryFor = (categoryName: string) => { const mappings: Record = { - "science & tech": Category.SciTech, + "science & technology": Category.SciTech, "arts & humanities": Category.ArtsHumanities, politics: Category.Politics, media: Category.Media, @@ -79,3 +79,8 @@ export const categoriesToIconsMap: Record = { [Category.Education]: School, [Category.Others]: CircleHelp, }; + +export const getIconFor = (categoryName: string) => { + const category = getCategoryFor(categoryName); + return categoriesToIconsMap[category]; +};