From 7655722a3c2de09ef78ec1a9005f34838bb0235b Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Tue, 29 Oct 2024 11:42:42 +0800 Subject: [PATCH 01/27] Add new Carousel shadcn component --- frontend/components/ui/carousel.tsx | 262 ++++++++++++++++++++++++++++ frontend/package-lock.json | 26 +++ frontend/package.json | 1 + 3 files changed, 289 insertions(+) create mode 100644 frontend/components/ui/carousel.tsx diff --git a/frontend/components/ui/carousel.tsx b/frontend/components/ui/carousel.tsx new file mode 100644 index 00000000..ad434ddf --- /dev/null +++ b/frontend/components/ui/carousel.tsx @@ -0,0 +1,262 @@ +"use client"; + +import * as React from "react"; +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; + +type CarouselProps = { + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: "horizontal" | "vertical"; + setApi?: (api: CarouselApi) => void; +}; + +type CarouselContextProps = { + carouselRef: ReturnType[0]; + api: ReturnType[1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; + +const CarouselContext = React.createContext(null); + +function useCarousel() { + const context = React.useContext(CarouselContext); + + if (!context) { + throw new Error("useCarousel must be used within a "); + } + + return context; +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref, + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins, + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return; + } + + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev(); + }, [api]); + + const scrollNext = React.useCallback(() => { + api?.scrollNext(); + }, [api]); + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault(); + scrollPrev(); + } else if (event.key === "ArrowRight") { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrev, scrollNext], + ); + + React.useEffect(() => { + if (!api || !setApi) { + return; + } + + setApi(api); + }, [api, setApi]); + + React.useEffect(() => { + if (!api) { + return; + } + + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); + + return () => { + api?.off("select", onSelect); + }; + }, [api, onSelect]); + + return ( + +
+ {children} +
+
+ ); + }, +); +Carousel.displayName = "Carousel"; + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel(); + + return ( +
+
+
+ ); +}); +CarouselContent.displayName = "CarouselContent"; + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel(); + + return ( +
+ ); +}); +CarouselItem.displayName = "CarouselItem"; + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); + + return ( + + ); +}); +CarouselPrevious.displayName = "CarouselPrevious"; + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel(); + + return ( + + ); +}); +CarouselNext.displayName = "CarouselNext"; + +export { + Carousel, + type CarouselApi, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +}; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ff49c2b2..d793ec0b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,6 +30,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "embla-carousel-react": "^8.3.0", "lucide-react": "^0.441.0", "next": "14.2.12", "qs": "^6.13.0", @@ -3734,6 +3735,31 @@ "dev": true, "license": "ISC" }, + "node_modules/embla-carousel": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.3.0.tgz", + "integrity": "sha512-Ve8dhI4w28qBqR8J+aMtv7rLK89r1ZA5HocwFz6uMB/i5EiC7bGI7y+AM80yAVUJw3qqaZYK7clmZMUR8kM3UA==" + }, + "node_modules/embla-carousel-react": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.3.0.tgz", + "integrity": "sha512-P1FlinFDcIvggcErRjNuVqnUR8anyo8vLMIH8Rthgofw7Nj8qTguCa2QjFAbzxAUTQTPNNjNL7yt0BGGinVdFw==", + "dependencies": { + "embla-carousel": "8.3.0", + "embla-carousel-reactive-utils": "8.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.3.0.tgz", + "integrity": "sha512-EYdhhJ302SC4Lmkx8GRsp0sjUhEN4WyFXPOk0kGu9OXZSRMmcBlRgTvHcq8eKJE1bXWBsOi1T83B+BSSVZSmwQ==", + "peerDependencies": { + "embla-carousel": "8.3.0" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index b4cf23f6..af592767 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,6 +32,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "embla-carousel-react": "^8.3.0", "lucide-react": "^0.441.0", "next": "14.2.12", "qs": "^6.13.0", From f522e364996eb39528997948ee14201ee64a0a91 Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 01:40:13 +0800 Subject: [PATCH 02/27] Use carousel on landing page -Put the Cards in the 'Learn how Jippy can help' section in a Carousel to improve responsiveness across all breakpoints --- frontend/app/landing.tsx | 109 +++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index 33dd196d..eb257029 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -23,11 +23,16 @@ import { tierIDToTierName, TierPrice, } from "@/types/billing"; +import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; +import useBreakpointMediaQuery from "@/hooks/use-breakpoint-media-query"; +import { MediaBreakpoint } from "@/utils/media"; const Landing = () => { + const mediaBreakpoint = useBreakpointMediaQuery(); + return (
-
+
{

We've been there. Learn how Jippy can help.

-
- - - - Saves you time - - - No more combing through the web for hours to find examples. - Jippy sifts through the mountain of news articles to bring the - most interesting events going on around the world right to - you. - - - +
+ + + +
+ + + + Saves you time + + + No more combing through the web for hours to find examples. + Jippy sifts through the mountain of news articles to bring the + most interesting events going on around the world right to + you. + + + +
+
- - - - Helps you build your example bank - - - Keeping up to date with current affairs is important for - scoring well in GP. Jippy is here to encourage everyone to - read the news by making it accessible. - - - + +
+ + + + Helps you build your example bank + + + Keeping up to date with current affairs is important for + scoring well in GP. Jippy is here to encourage everyone to + read the news by making it accessible. + + + +
+
- - - - Insights and analysis - - - Ever tried reading the news and forget it immediately? Or - wonder how to even apply the knowledge to your GP essays? Not - anymore. Jippy can intelligently extract relevant GP analysis - and even generate essay points. - - - + +
+ + + + Insights and analysis + + + Ever tried reading the news and forget it immediately? Or + wonder how to even apply the knowledge to your GP essays? Not + anymore. Jippy can intelligently extract relevant GP analysis + and even generate essay points. + + + +
+
+
+ { mediaBreakpoint === MediaBreakpoint.Sm && ( + <> + + + + ) + + } +
From 8ba6415a2c2ef9f900361c7f74551cb99d50e666 Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 01:53:35 +0800 Subject: [PATCH 03/27] Fix carousel looping on landing page -Force overflow of the carousel on the landing page so that the cards can now loop around -This also fixes center aligning of the carousels --- frontend/app/landing.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index eb257029..56939f21 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -72,7 +72,7 @@ const Landing = () => {
- +
@@ -90,7 +90,7 @@ const Landing = () => {
- +
@@ -107,7 +107,7 @@ const Landing = () => {
- +
From 690528520c1ccd2e8e06281e4fe628afdf81a6cc Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 02:02:06 +0800 Subject: [PATCH 04/27] Fix uneven gap in carousel -Remove padding set on CarouselContent component to ensure even gap between every pair of adjacent carousel cards --- frontend/app/landing.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index 56939f21..b904921d 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -71,7 +71,7 @@ const Landing = () => {
- +
From 07ca00efb21477fd7061c6a23ad4cf9a57df6e13 Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 02:22:24 +0800 Subject: [PATCH 05/27] Add new CarouselPagination component -Add CarouselPagination component that provides a dot menu for a given Carousel such that clicking each dot scrolls the carousel to the corresponding carousel slide/card --- frontend/components/ui/carousel.tsx | 51 +++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/frontend/components/ui/carousel.tsx b/frontend/components/ui/carousel.tsx index ad434ddf..849f2af7 100644 --- a/frontend/components/ui/carousel.tsx +++ b/frontend/components/ui/carousel.tsx @@ -252,6 +252,56 @@ const CarouselNext = React.forwardRef< }); CarouselNext.displayName = "CarouselNext"; +const CarouselPagination = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { api } = useCarousel(); + const [selectedIndex, setSelectedIndex] = React.useState(0); + const [scrollSnaps, setScrollSnaps] = React.useState([]); + + React.useEffect(() => { + if (!api) { + return; + } + + const onSelect = () => { + setSelectedIndex(api.selectedScrollSnap()); + }; + + setScrollSnaps(api.scrollSnapList()); + + api.on('select', onSelect); + // Initialize the selected index + onSelect(); + + return () => { + api.off('select', onSelect); + }; + }, [api]); + + return ( +
+ {scrollSnaps.map((_, index) => ( +
+ ); +}); +CarouselPagination.displayName = 'CarouselPagination'; + export { Carousel, type CarouselApi, @@ -259,4 +309,5 @@ export { CarouselItem, CarouselNext, CarouselPrevious, + CarouselPagination, }; From a19f78b452656a5177642707f131da7fdf703e8a Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 02:24:16 +0800 Subject: [PATCH 06/27] Use CarouselPagination on landing page -Add the CarouselPagination component to the landing page below the carousel cards -This makes it much clearer that the component is a carousel and provides a consistent way(across all breakpoints) to navigate through the carousel cards --- frontend/app/landing.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index b904921d..b7d65fc0 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -23,7 +23,7 @@ import { tierIDToTierName, TierPrice, } from "@/types/billing"; -import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; +import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPagination, CarouselPrevious } from "@/components/ui/carousel"; import useBreakpointMediaQuery from "@/hooks/use-breakpoint-media-query"; import { MediaBreakpoint } from "@/utils/media"; @@ -130,9 +130,8 @@ const Landing = () => { - ) - - } + )} +
From fd2eecc0aed94f98ebe7f7b36b230d146942534d Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 03:10:24 +0800 Subject: [PATCH 07/27] Fix mobile responsiveness of landing page -Switch from using grid layout and col-span sizes to flex and flex-basis for maintaining a set width ratio between components in the 'Learn how Jippy AI works' and 'Frequently asked questions' sections -This fixes horizontal overflow of the aforementioned sections for medium media breakpoint --- frontend/app/landing.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index b7d65fc0..c751e8f6 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -136,8 +136,8 @@ const Landing = () => {
-
-
+
+
Learn how Jippy AI works @@ -147,7 +147,7 @@ const Landing = () => {
-
+

Jippy is after all just an AI frog, and can make mistakes, but here are some things made sure to train Jippy on to avoid mistakes @@ -187,8 +187,8 @@ const Landing = () => {

-
-
+
+

Frequently asked questions

@@ -197,7 +197,7 @@ const Landing = () => {
-
+
Date: Wed, 30 Oct 2024 03:24:51 +0800 Subject: [PATCH 08/27] Improve landing page layout -Standardise margins and gaps for sections on the landing page -Remove redundant gap className --- frontend/app/landing.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index c751e8f6..9a00972d 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -71,7 +71,7 @@ const Landing = () => {
- +
@@ -168,10 +168,10 @@ const Landing = () => {

Plans & pricing

-

+

We strive to keep Jippy accessible for everyone.

-
+
Date: Wed, 30 Oct 2024 03:55:32 +0800 Subject: [PATCH 09/27] Fix landing page carousel height -Make all carousel cards the same height so that CarouselPagination is always rendered right below the current CarouselItem on display --- frontend/app/landing.tsx | 90 +++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index 9a00972d..6a9c912b 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -71,58 +71,52 @@ const Landing = () => {
- - -
- - - - Saves you time - - - No more combing through the web for hours to find examples. - Jippy sifts through the mountain of news articles to bring the - most interesting events going on around the world right to - you. - - - -
+ + + + + + Saves you time + + + No more combing through the web for hours to find examples. + Jippy sifts through the mountain of news articles to bring the + most interesting events going on around the world right to + you. + + + - -
- - - - Helps you build your example bank - - - Keeping up to date with current affairs is important for - scoring well in GP. Jippy is here to encourage everyone to - read the news by making it accessible. - - - -
+ + + + + Helps you build your example bank + + + Keeping up to date with current affairs is important for + scoring well in GP. Jippy is here to encourage everyone to + read the news by making it accessible. + + + - -
- - - - Insights and analysis - - - Ever tried reading the news and forget it immediately? Or - wonder how to even apply the knowledge to your GP essays? Not - anymore. Jippy can intelligently extract relevant GP analysis - and even generate essay points. - - - -
+ + + + + Insights and analysis + + + Ever tried reading the news and forget it immediately? Or + wonder how to even apply the knowledge to your GP essays? Not + anymore. Jippy can intelligently extract relevant GP analysis + and even generate essay points. + + +
{ mediaBreakpoint === MediaBreakpoint.Sm && ( From 0fad0623e80db8a93dbee2d4d3eb50f10ff5ca6c Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 03:59:23 +0800 Subject: [PATCH 10/27] Remove carousel buttons on landing page -Remove next/previous buttons for the carousel on the landing page for small media breakpoint because these buttons are reducing the carousel card width, making user very visually tense -This improves readability of the text in every carousel card on small media breakpoint as well --- frontend/app/landing.tsx | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index 6a9c912b..1ec5d32e 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -23,12 +23,9 @@ import { tierIDToTierName, TierPrice, } from "@/types/billing"; -import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPagination, CarouselPrevious } from "@/components/ui/carousel"; -import useBreakpointMediaQuery from "@/hooks/use-breakpoint-media-query"; -import { MediaBreakpoint } from "@/utils/media"; +import { Carousel, CarouselContent, CarouselItem, CarouselPagination } from "@/components/ui/carousel"; const Landing = () => { - const mediaBreakpoint = useBreakpointMediaQuery(); return (
@@ -69,7 +66,7 @@ const Landing = () => {

We've been there. Learn how Jippy can help.

-
+
@@ -119,12 +116,6 @@ const Landing = () => { - { mediaBreakpoint === MediaBreakpoint.Sm && ( - <> - - - - )}
From 5213df8ce07bf9516cd992cd361a42faca61615d Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 04:54:17 +0800 Subject: [PATCH 11/27] Fix responsiveness of billing page -Remove overflow-x-auto and use flexbox layout to switch between row and col layout for pricing tiers depending on the viewport size -This fixes #339 --- frontend/components/billing/pricing-table.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/billing/pricing-table.tsx b/frontend/components/billing/pricing-table.tsx index a0215e6f..0f0bf112 100644 --- a/frontend/components/billing/pricing-table.tsx +++ b/frontend/components/billing/pricing-table.tsx @@ -8,13 +8,13 @@ interface PricingTiers { const PricingTable = ({ tiers }: PricingTiers) => { return ( -
+
{tiers?.length > 0 ? ( tiers.map((tier, index) => { return ( Date: Wed, 30 Oct 2024 04:56:24 +0800 Subject: [PATCH 12/27] Fix billing page layout -Fix billing page components being misaligned compared to the heading on small media breakpoints --- frontend/app/(authenticated)/user/billing/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/(authenticated)/user/billing/page.tsx b/frontend/app/(authenticated)/user/billing/page.tsx index bffee0ee..347e9c08 100644 --- a/frontend/app/(authenticated)/user/billing/page.tsx +++ b/frontend/app/(authenticated)/user/billing/page.tsx @@ -155,7 +155,7 @@ const Page = () => {

Billing

-
+

Your Tier

{userTier} Tier:

@@ -176,7 +176,7 @@ const Page = () => { )}
-
+

Our Tiers

From 01fc7550ea6e0d824fb8e2027c44cb974b271539 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 05:07:40 +0800 Subject: [PATCH 13/27] fix(home): singapore filter --- .../home/action-bar-mobile.tsx | 4 +- .../app/(authenticated)/home/article-feed.tsx | 81 +++++++++++++++++-- .../(authenticated)/home/top-article-list.tsx | 69 ++++++++-------- 3 files changed, 111 insertions(+), 43 deletions(-) diff --git a/frontend/app/(authenticated)/home/action-bar-mobile.tsx b/frontend/app/(authenticated)/home/action-bar-mobile.tsx index 55e0d586..41db5a4b 100644 --- a/frontend/app/(authenticated)/home/action-bar-mobile.tsx +++ b/frontend/app/(authenticated)/home/action-bar-mobile.tsx @@ -16,7 +16,7 @@ const ActionBarMobile = () => { - + { - + { const user = useUserStore((state) => state.user); + const [singaporeOnly, setSingaporeOnly] = useState(false); const eventStartDate = new Date("11 september 2024"); @@ -23,7 +34,7 @@ const ArticleFeed = () => { getArticles( toQueryDate(eventStartDate), 10, - false, + singaporeOnly, user?.categories.map((category) => category.id), ), ); @@ -43,13 +54,63 @@ const ArticleFeed = () => { Today's articles for you - - - +
+ +
+
+ +
{!isLoading && articles?.data && ( <>
@@ -65,6 +126,12 @@ const ArticleFeed = () => { )} + + +
); }; diff --git a/frontend/app/(authenticated)/home/top-article-list.tsx b/frontend/app/(authenticated)/home/top-article-list.tsx index 7c85ed85..db4e301f 100644 --- a/frontend/app/(authenticated)/home/top-article-list.tsx +++ b/frontend/app/(authenticated)/home/top-article-list.tsx @@ -1,6 +1,9 @@ "use client"; import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; + +import ArticlesList from "@/app/(authenticated)/articles/articles-list"; import { Select, SelectContent, @@ -9,17 +12,14 @@ import { SelectLabel, SelectTrigger, SelectValue, -} from "@radix-ui/react-select"; -import { useQuery } from "@tanstack/react-query"; - -import ArticlesList from "@/app/(authenticated)/articles/articles-list"; +} from "@/components/ui/select"; import { getTopArticles } from "@/queries/article"; import ArticleCard from "./article-card"; const TopArticleList = () => { - const { data, isLoading } = useQuery(getTopArticles(false)); const [singaporeOnly, setSingaporeOnly] = useState(false); + const { data, isLoading } = useQuery(getTopArticles(singaporeOnly)); const numberArticles = isLoading ? undefined : data?.length; if (isLoading || data === undefined || numberArticles === 0) { @@ -32,38 +32,39 @@ const TopArticleList = () => { return (
-

- This week's top articles -

-
- + setSingaporeOnly(value === "singapore-only") } > - - - - - Article filter - - Global - - - Singapore - - - - + + + + + + Article filter + + Global + + + Singapore + + + + +
-
{data?.map((article) => ( From 2f214e1f9fa20bbfa08c8fc0fae941fbba2fcbb7 Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 05:12:41 +0800 Subject: [PATCH 14/27] Fix billing page layout again -Also fix height, width and padding of components in the 'Our Tiers' section of the billing page --- frontend/app/(authenticated)/user/billing/page.tsx | 8 ++++---- frontend/components/billing/pricing-table.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app/(authenticated)/user/billing/page.tsx b/frontend/app/(authenticated)/user/billing/page.tsx index 347e9c08..a47b7b29 100644 --- a/frontend/app/(authenticated)/user/billing/page.tsx +++ b/frontend/app/(authenticated)/user/billing/page.tsx @@ -151,11 +151,11 @@ const Page = () => { return ( user && (
-
+

Billing

-
-
+
+

Your Tier

{userTier} Tier:

@@ -176,7 +176,7 @@ const Page = () => { )}
-
+

Our Tiers

diff --git a/frontend/components/billing/pricing-table.tsx b/frontend/components/billing/pricing-table.tsx index 0f0bf112..1dd0deca 100644 --- a/frontend/components/billing/pricing-table.tsx +++ b/frontend/components/billing/pricing-table.tsx @@ -8,13 +8,13 @@ interface PricingTiers { const PricingTable = ({ tiers }: PricingTiers) => { return ( -
+
{tiers?.length > 0 ? ( tiers.map((tier, index) => { return ( Date: Wed, 30 Oct 2024 05:16:55 +0800 Subject: [PATCH 15/27] Fix eslint style issues --- frontend/app/landing.tsx | 30 ++++++++++++++++------------- frontend/components/ui/carousel.tsx | 18 ++++++++--------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index 1ec5d32e..6affe36c 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -16,6 +16,12 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselPagination, +} from "@/components/ui/carousel"; import { JippyTierID, tierIDToTierDescription, @@ -23,10 +29,8 @@ import { tierIDToTierName, TierPrice, } from "@/types/billing"; -import { Carousel, CarouselContent, CarouselItem, CarouselPagination } from "@/components/ui/carousel"; const Landing = () => { - return (
@@ -67,7 +71,7 @@ const Landing = () => { We've been there. Learn how Jippy can help.
- + @@ -76,10 +80,10 @@ const Landing = () => { Saves you time - No more combing through the web for hours to find examples. - Jippy sifts through the mountain of news articles to bring the - most interesting events going on around the world right to - you. + No more combing through the web for hours to find + examples. Jippy sifts through the mountain of news + articles to bring the most interesting events going on + around the world right to you. @@ -93,8 +97,8 @@ const Landing = () => { Keeping up to date with current affairs is important for - scoring well in GP. Jippy is here to encourage everyone to - read the news by making it accessible. + scoring well in GP. Jippy is here to encourage everyone + to read the news by making it accessible. @@ -107,10 +111,10 @@ const Landing = () => { Insights and analysis - Ever tried reading the news and forget it immediately? Or - wonder how to even apply the knowledge to your GP essays? Not - anymore. Jippy can intelligently extract relevant GP analysis - and even generate essay points. + Ever tried reading the news and forget it immediately? + Or wonder how to even apply the knowledge to your GP + essays? Not anymore. Jippy can intelligently extract + relevant GP analysis and even generate essay points. diff --git a/frontend/components/ui/carousel.tsx b/frontend/components/ui/carousel.tsx index 849f2af7..a2e0e820 100644 --- a/frontend/components/ui/carousel.tsx +++ b/frontend/components/ui/carousel.tsx @@ -271,36 +271,36 @@ const CarouselPagination = React.forwardRef< setScrollSnaps(api.scrollSnapList()); - api.on('select', onSelect); + api.on("select", onSelect); // Initialize the selected index onSelect(); return () => { - api.off('select', onSelect); + api.off("select", onSelect); }; }, [api]); return (
{scrollSnaps.map((_, index) => (
); }); -CarouselPagination.displayName = 'CarouselPagination'; +CarouselPagination.displayName = "CarouselPagination"; export { Carousel, @@ -308,6 +308,6 @@ export { CarouselContent, CarouselItem, CarouselNext, - CarouselPrevious, CarouselPagination, + CarouselPrevious, }; From d32c12884df4f1b82eb0d177d97c3e00d64bb551 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 05:37:26 +0800 Subject: [PATCH 16/27] fix(article): align left text in accordion --- frontend/app/(authenticated)/articles/[id]/article-concepts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/(authenticated)/articles/[id]/article-concepts.tsx b/frontend/app/(authenticated)/articles/[id]/article-concepts.tsx index 1c7ebc88..e66cf6f8 100644 --- a/frontend/app/(authenticated)/articles/[id]/article-concepts.tsx +++ b/frontend/app/(authenticated)/articles/[id]/article-concepts.tsx @@ -206,7 +206,7 @@ const ArticleConcepts = ({ article, showAnnotations }: Props) => { chevronClassName="h-6 w-6 stroke-[2.5]" className="text-xl text-cyan-600 font-semibold" > - + {concept.name} From 3e2a47269c140e65bc1701257c177db7477b80ce Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 05:41:04 +0800 Subject: [PATCH 17/27] feat: add beta tag to essay feedback --- frontend/app/(authenticated)/essay-feedback/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/app/(authenticated)/essay-feedback/page.tsx b/frontend/app/(authenticated)/essay-feedback/page.tsx index b69c85dc..ed4c99b6 100644 --- a/frontend/app/(authenticated)/essay-feedback/page.tsx +++ b/frontend/app/(authenticated)/essay-feedback/page.tsx @@ -8,6 +8,7 @@ import { BookCheckIcon, BookOpenCheckIcon, SparklesIcon } from "lucide-react"; import { z } from "zod"; import { createEssayEssaysPost, ParagraphType } from "@/client"; +import Chip from "@/components/display/chip"; import { AutosizeTextarea } from "@/components/ui/autosize-textarea"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; @@ -176,6 +177,7 @@ const EssayFeedbackPage = () => {

Get essay feedback

+

Get feedback on your GP essay at the snap of your fingers. Rest From 36e3b5bdba0e2f2c583d2b6498b83476ff16ec2f Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 05:58:47 +0800 Subject: [PATCH 18/27] fix: add confirmation for downgrade --- .../article-annotations/analysis-notes.tsx | 6 +++--- .../mini-generic-concept-note.tsx | 6 +++--- .../[id]/article-annotations/note-item.tsx | 6 +++--- .../history/essay-list-card.tsx | 6 +++--- .../app/(authenticated)/user/billing/page.tsx | 5 ++++- frontend/components/billing/pricing-tier.tsx | 20 +++++++++++++++++-- .../dialog/{DeleteDialog.tsx => Dialog.tsx} | 9 ++++----- 7 files changed, 38 insertions(+), 20 deletions(-) rename frontend/components/dialog/{DeleteDialog.tsx => Dialog.tsx} (86%) diff --git a/frontend/app/(authenticated)/articles/[id]/article-annotations/analysis-notes.tsx b/frontend/app/(authenticated)/articles/[id]/article-annotations/analysis-notes.tsx index 6ad64ee4..34e2f266 100644 --- a/frontend/app/(authenticated)/articles/[id]/article-annotations/analysis-notes.tsx +++ b/frontend/app/(authenticated)/articles/[id]/article-annotations/analysis-notes.tsx @@ -8,7 +8,7 @@ import { } from "lucide-react"; import { NoteDTO } from "@/client"; -import DeleteDialog from "@/components/dialog/DeleteDialog"; +import Dialog from "@/components/dialog/Dialog"; import { Button } from "@/components/ui/button"; import { Popover, @@ -43,8 +43,8 @@ const AnalysisNotes = ({ return (
{deleteDialogOpen ? ( - setDeleteDialogOpen(0)} onDelete={() => onDelete(deleteDialogOpen)} /> diff --git a/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx b/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx index 71d27188..3d76a814 100644 --- a/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx +++ b/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx @@ -3,7 +3,7 @@ import { SubmitHandler } from "react-hook-form"; import { EditIcon, TrashIcon } from "lucide-react"; import { ArticleConceptDTO, NoteDTO } from "@/client"; -import DeleteDialog from "@/components/dialog/DeleteDialog"; +import Dialog from "@/components/dialog/Dialog"; import CategoryChip from "@/components/display/category-chip"; import { Button } from "@/components/ui/button"; import { useDeleteNote, useEditArticleNote } from "@/queries/note"; @@ -96,8 +96,8 @@ const MiniGenericConceptNote = ({ id={`annotation-${note.id}`} > {deleteDialogOpen && ( - setDeleteDialogOpen(false)} onDelete={() => { deleteNoteMutation.mutate(note.id); diff --git a/frontend/app/(authenticated)/articles/[id]/article-annotations/note-item.tsx b/frontend/app/(authenticated)/articles/[id]/article-annotations/note-item.tsx index d9d49435..5556e91a 100644 --- a/frontend/app/(authenticated)/articles/[id]/article-annotations/note-item.tsx +++ b/frontend/app/(authenticated)/articles/[id]/article-annotations/note-item.tsx @@ -3,7 +3,7 @@ import { SubmitHandler } from "react-hook-form"; import { EditIcon, TrashIcon } from "lucide-react"; import { NoteDTO } from "@/client"; -import DeleteDialog from "@/components/dialog/DeleteDialog"; +import Dialog from "@/components/dialog/Dialog"; import CategoryChip from "@/components/display/category-chip"; import { Button } from "@/components/ui/button"; import { useDeleteNote } from "@/queries/note"; @@ -29,8 +29,8 @@ const NoteItem = ({ note, articleId, handleEditNote }: NoteItemProps) => { key={note.id} > {deleteDialogOpen && ( - setDeleteDialogOpen(false)} onDelete={() => { deleteNoteMutation.mutate(note.id); diff --git a/frontend/app/(authenticated)/essay-feedback/history/essay-list-card.tsx b/frontend/app/(authenticated)/essay-feedback/history/essay-list-card.tsx index ba86efb6..abebe5a3 100644 --- a/frontend/app/(authenticated)/essay-feedback/history/essay-list-card.tsx +++ b/frontend/app/(authenticated)/essay-feedback/history/essay-list-card.tsx @@ -13,7 +13,7 @@ import { } from "lucide-react"; import { EssayMiniDTO } from "@/client/types.gen"; -import DeleteDialog from "@/components/dialog/DeleteDialog"; +import Dialog from "@/components/dialog/Dialog"; import { DropdownMenu, DropdownMenuContent, @@ -37,8 +37,8 @@ const EssayListCard = ({ essay }: EssayListCardProps) => { return (
{deleteDialogOpen && ( - setDeleteDialogOpen(false)} onDelete={() => { deleteEssayMutation.mutate(essay.id); diff --git a/frontend/app/(authenticated)/user/billing/page.tsx b/frontend/app/(authenticated)/user/billing/page.tsx index a47b7b29..ce8ac1c2 100644 --- a/frontend/app/(authenticated)/user/billing/page.tsx +++ b/frontend/app/(authenticated)/user/billing/page.tsx @@ -26,6 +26,9 @@ import { const FREE_TIER_ID = 1; const TIER_STATUS_ACTIVE = "active"; +// Used by pricing-tier to figure out if it is a downgrade +export const DOWNGRADE_TEXT = "Downgrade"; + const getPriceButtonText = ( priceTierId: number, user: UserPublic | undefined, @@ -36,7 +39,7 @@ const getPriceButtonText = ( } else if (priceTierId > userTierId) { return "Upgrade"; } else if (priceTierId < userTierId) { - return "Downgrade"; + return DOWNGRADE_TEXT; } else { return "Buy"; } diff --git a/frontend/components/billing/pricing-tier.tsx b/frontend/components/billing/pricing-tier.tsx index b3ab23b4..bcd45701 100644 --- a/frontend/components/billing/pricing-tier.tsx +++ b/frontend/components/billing/pricing-tier.tsx @@ -1,3 +1,9 @@ +"use client"; + +import { useState } from "react"; + +import { DOWNGRADE_TEXT } from "@/app/(authenticated)/user/billing/page"; +import Dialog from "@/components/dialog/Dialog"; import { Button } from "@/components/ui/button"; export interface PricingTierInfo { @@ -23,7 +29,8 @@ const PricingTier = ({ tierFeatures, }: PricingTierInfo) => { const hasButton = onClickBuy && buttonText; - + const isDowngrade = buttonText === DOWNGRADE_TEXT; + const [downgradeDialogOpen, setDowngradeDialogOpen] = useState(false); return (
@@ -38,11 +45,20 @@ const PricingTier = ({
{tierDescription}
+ {isDowngrade && downgradeDialogOpen && ( + setDowngradeDialogOpen(false)} + onDelete={onClickBuy!} + /> + )} {hasButton && ( diff --git a/frontend/components/dialog/DeleteDialog.tsx b/frontend/components/dialog/Dialog.tsx similarity index 86% rename from frontend/components/dialog/DeleteDialog.tsx rename to frontend/components/dialog/Dialog.tsx index cb78af0e..3acb1456 100644 --- a/frontend/components/dialog/DeleteDialog.tsx +++ b/frontend/components/dialog/Dialog.tsx @@ -13,11 +13,11 @@ import { interface OwnProps { onDelete: () => void; - label: string; + action: string; onClose: () => void; } -const DeleteDialog = ({ onDelete, label, onClose }: OwnProps) => { +const Dialog = ({ onDelete, action: label, onClose }: OwnProps) => { return ( { @@ -30,8 +30,7 @@ const DeleteDialog = ({ onDelete, label, onClose }: OwnProps) => { Are you absolutely sure? - This action cannot be undone. Are you sure you want to delete this{" "} - {label}? + This action cannot be undone. Are you sure you want to {label}? @@ -43,4 +42,4 @@ const DeleteDialog = ({ onDelete, label, onClose }: OwnProps) => { ); }; -export default DeleteDialog; +export default Dialog; From 0bc3d15b4fb9040e3d309ebbf0bb20cb8a16998a Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 05:59:21 +0800 Subject: [PATCH 19/27] fix: remove hardcoded date in article feed --- frontend/app/(authenticated)/home/article-feed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/(authenticated)/home/article-feed.tsx b/frontend/app/(authenticated)/home/article-feed.tsx index 1a9d0b59..6caa6595 100644 --- a/frontend/app/(authenticated)/home/article-feed.tsx +++ b/frontend/app/(authenticated)/home/article-feed.tsx @@ -26,7 +26,7 @@ const ArticleFeed = () => { const user = useUserStore((state) => state.user); const [singaporeOnly, setSingaporeOnly] = useState(false); - const eventStartDate = new Date("11 september 2024"); + const eventStartDate = new Date(); eventStartDate.setDate(eventStartDate.getDate() - 1); From c87412e87433bec139ff294728b2967b69882354 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 06:00:17 +0800 Subject: [PATCH 20/27] fix: note action label --- .../[id]/article-annotations/mini-generic-concept-note.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx b/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx index 3d76a814..4269d4f9 100644 --- a/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx +++ b/frontend/app/(authenticated)/articles/[id]/article-annotations/mini-generic-concept-note.tsx @@ -97,7 +97,7 @@ const MiniGenericConceptNote = ({ > {deleteDialogOpen && ( setDeleteDialogOpen(false)} onDelete={() => { deleteNoteMutation.mutate(note.id); From 23de89a0b902590f7f37020edac37ae3779ed029 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 06:04:40 +0800 Subject: [PATCH 21/27] fix: build error --- frontend/app/(authenticated)/user/billing/page.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/app/(authenticated)/user/billing/page.tsx b/frontend/app/(authenticated)/user/billing/page.tsx index ce8ac1c2..439f69de 100644 --- a/frontend/app/(authenticated)/user/billing/page.tsx +++ b/frontend/app/(authenticated)/user/billing/page.tsx @@ -26,9 +26,6 @@ import { const FREE_TIER_ID = 1; const TIER_STATUS_ACTIVE = "active"; -// Used by pricing-tier to figure out if it is a downgrade -export const DOWNGRADE_TEXT = "Downgrade"; - const getPriceButtonText = ( priceTierId: number, user: UserPublic | undefined, @@ -39,7 +36,9 @@ const getPriceButtonText = ( } else if (priceTierId > userTierId) { return "Upgrade"; } else if (priceTierId < userTierId) { - return DOWNGRADE_TEXT; + // Used by pricing-tier to figure out if it is a downgrade. + // Changing this text will break the dialog. + return "Downgrade"; } else { return "Buy"; } From 8df22dcd79f9327a09b7d5d57b63ca57344dac49 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 06:06:41 +0800 Subject: [PATCH 22/27] fix: build error --- frontend/components/billing/pricing-tier.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/components/billing/pricing-tier.tsx b/frontend/components/billing/pricing-tier.tsx index bcd45701..c391f561 100644 --- a/frontend/components/billing/pricing-tier.tsx +++ b/frontend/components/billing/pricing-tier.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; -import { DOWNGRADE_TEXT } from "@/app/(authenticated)/user/billing/page"; import Dialog from "@/components/dialog/Dialog"; import { Button } from "@/components/ui/button"; @@ -18,6 +17,8 @@ export interface PricingTierInfo { onClickBuy?: () => void; } +const DOWNGRADE_TEXT = "Downgrade"; + const PricingTier = ({ className, tierName, From 44696a911d761108392ffd948728f5d87b7bc412 Mon Sep 17 00:00:00 2001 From: seeleng Date: Wed, 30 Oct 2024 06:34:57 +0800 Subject: [PATCH 23/27] fix(landing): typos --- frontend/app/landing.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx index 6affe36c..0ad651f2 100644 --- a/frontend/app/landing.tsx +++ b/frontend/app/landing.tsx @@ -32,7 +32,7 @@ import { const Landing = () => { return ( -
+
{ provide fall-backs instead during essay generation.
  • - Jippy is trained on a set real current affair news data set. We - currently have 13k articles in our dataset. + Jippy is trained on a set of real current affairs news data set. + We currently have 13k articles in our dataset.
  • @@ -181,13 +181,19 @@ const Landing = () => {

    Frequently asked questions

    - - Still curious? Drop us an email at jippythefrog@gmail.com - +
    + Still curious? Drop us an email at + + jippythefrog@gmail.com + +
    - + { Cannot just ChatGPT meh? - Jippy is trained on 13k datasets, and built specially for A - Level GP students. + Jippy is trained on a dataset with 13k articles and built + specially for A Level GP students. From b212771b22e19d24ca4a829b5d355fc22f4f88dd Mon Sep 17 00:00:00 2001 From: Wang Haoyang Date: Wed, 30 Oct 2024 06:36:28 +0800 Subject: [PATCH 24/27] Improve layout of essay feedback page -Organise layout of essay feedback page on mobile so it looks more aesthetic --- .../(authenticated)/essay-feedback/page.tsx | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/frontend/app/(authenticated)/essay-feedback/page.tsx b/frontend/app/(authenticated)/essay-feedback/page.tsx index b69c85dc..0d258534 100644 --- a/frontend/app/(authenticated)/essay-feedback/page.tsx +++ b/frontend/app/(authenticated)/essay-feedback/page.tsx @@ -169,12 +169,12 @@ const EssayFeedbackPage = () => { return (
    -
    +
    - +

    Get essay feedback

    @@ -183,51 +183,49 @@ const EssayFeedbackPage = () => { anything else than providing you feedback.

    -
    -
    -
    - -
    - ( - - - - - - - )} - /> - ( - - - - - - - )} - /> -
    - -
    - -
    +
    +
    + +
    + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> +
    + +
    +
    ); From 7fccfb4a72348edf0dfd5c1c3335850cfbbc6df7 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Wed, 30 Oct 2024 06:53:40 +0800 Subject: [PATCH 25/27] feat: my brain is dead --- .../(authenticated)/articles/[id]/page.tsx | 2 +- .../app/(authenticated)/articles/page.tsx | 39 ++++++++++++++++ .../(authenticated)/categories/[id]/page.tsx | 41 +++++++++++++++++ .../navigation/sidebar/sidebar-topics.tsx | 2 +- .../components/navigation/sidebar/sidebar.tsx | 46 ++++++++++--------- 5 files changed, 107 insertions(+), 23 deletions(-) diff --git a/frontend/app/(authenticated)/articles/[id]/page.tsx b/frontend/app/(authenticated)/articles/[id]/page.tsx index 013cf5f8..fc14a6f7 100644 --- a/frontend/app/(authenticated)/articles/[id]/page.tsx +++ b/frontend/app/(authenticated)/articles/[id]/page.tsx @@ -97,7 +97,7 @@ const Page = ({ params }: { params: { id: string } }) => { return (
    { const [singaporeOnly, setSingaporeOnly] = useState(initialSingaporeOnly); + const { data: categories } = useQuery(getCategories()); const { data: articles, isSuccess: isArticlesLoaded } = useQuery( getArticlesPage( toQueryDate(eventStartDate), @@ -129,6 +132,42 @@ const Articles = () => { + +
    { const router = useRouter(); @@ -34,6 +35,7 @@ const Page = ({ params }: { params: { id: string } }) => { const [singaporeOnly, setSingaporeOnly] = useState(initialSingaporeOnly); + const user = useUserStore((state) => state.user); const { page, pageCount, getPageUrl } = usePagination({ totalCount }); const { data: articles, isSuccess: isArticlesLoaded } = useQuery( getArticlesForCategory(categoryId, page, singaporeOnly), @@ -107,6 +109,45 @@ const Page = ({ params }: { params: { id: string } }) => { + +
    diff --git a/frontend/components/navigation/sidebar/sidebar-topics.tsx b/frontend/components/navigation/sidebar/sidebar-topics.tsx index 7b9cc7e6..a24491eb 100644 --- a/frontend/components/navigation/sidebar/sidebar-topics.tsx +++ b/frontend/components/navigation/sidebar/sidebar-topics.tsx @@ -15,7 +15,7 @@ type Props = { }; const SidebarTopics = ({ label, categories }: Props) => { - const [isExpanded, setIsExpanded] = useState(true); + const [isExpanded, setIsExpanded] = useState(false); const numTopics = categories?.length; const pathname = usePathname(); diff --git a/frontend/components/navigation/sidebar/sidebar.tsx b/frontend/components/navigation/sidebar/sidebar.tsx index b54330df..812ae1fb 100644 --- a/frontend/components/navigation/sidebar/sidebar.tsx +++ b/frontend/components/navigation/sidebar/sidebar.tsx @@ -13,11 +13,11 @@ import { NotebookIcon, } from "lucide-react"; -import SidebarTopics from "@/components/navigation/sidebar/sidebar-topics"; import { getCategories } from "@/queries/category"; import { useUserStore } from "@/store/user/user-store-provider"; import SidebarItemWithIcon from "./sidebar-item-with-icon"; +import { Separator } from "@/components/ui/separator"; /* Assumption: This component is only rendered if the user is logged in */ const Sidebar = () => { @@ -26,14 +26,13 @@ const Sidebar = () => { const { data: categories, isSuccess } = useQuery(getCategories()); const user = useUserStore((state) => state.user); const userCategoryIds = user?.categories.map((category) => category.id) || []; - const otherCategories = categories?.filter( - (category) => !userCategoryIds.includes(category.id), - ); + // const otherCategories = categories?.filter( + // (category) => !userCategoryIds.includes(category.id), + // ); return ( -
    -
    - {/* TODO: active category */} +
    +
    { label="Articles" path="/articles" /> + + + + +
    + +
    { label="Notes" path="/notes" /> - - {
    {isSuccess && ( <> - - + {/* + */} )}
    From b6e4af72b6f2c49a29b66582f13b861bff0825f0 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Wed, 30 Oct 2024 07:22:19 +0800 Subject: [PATCH 26/27] feat: add tooltip --- .../app/(authenticated)/articles/page.tsx | 35 ++++- .../(authenticated)/categories/[id]/page.tsx | 31 ++++- .../components/navigation/sidebar/sidebar.tsx | 4 +- frontend/components/ui/tooltip.tsx | 30 +++++ frontend/package-lock.json | 125 ++++++++++++++++++ frontend/package.json | 1 + 6 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 frontend/components/ui/tooltip.tsx diff --git a/frontend/app/(authenticated)/articles/page.tsx b/frontend/app/(authenticated)/articles/page.tsx index b860896f..a83ae6f0 100644 --- a/frontend/app/(authenticated)/articles/page.tsx +++ b/frontend/app/(authenticated)/articles/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; @@ -18,12 +19,18 @@ import { } from "@/components/ui/select"; import usePagination from "@/hooks/use-pagination"; import { getArticlesPage } from "@/queries/article"; +import { getCategories } from "@/queries/category"; import { useUserStore } from "@/store/user/user-store-provider"; import { parseDate, toQueryDate } from "@/utils/date"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + import ArticlesList from "./articles-list"; -import { getCategories } from "@/queries/category"; -import Link from "next/link"; const DEFAULT_EVENT_PERIOD = Period.Week; @@ -149,9 +156,25 @@ const Articles = () => { Category filter - - My GP categories ({user?.categories.length}) - + + + + + My GP categories ({user?.categories.length}) + + + +
    + {user.categories + .map((category) => category.name) + .join(", ")} +
    +
    +
    +
    @@ -159,8 +182,8 @@ const Articles = () => { {categories?.map((category) => ( {category.name} diff --git a/frontend/app/(authenticated)/categories/[id]/page.tsx b/frontend/app/(authenticated)/categories/[id]/page.tsx index d93313af..56314ac2 100644 --- a/frontend/app/(authenticated)/categories/[id]/page.tsx +++ b/frontend/app/(authenticated)/categories/[id]/page.tsx @@ -23,6 +23,13 @@ import { getArticlesForCategory } from "@/queries/article"; import { getCategories } from "@/queries/category"; import { useUserStore } from "@/store/user/user-store-provider"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + const Page = ({ params }: { params: { id: string } }) => { const router = useRouter(); const searchParams = useSearchParams(); @@ -129,9 +136,25 @@ const Page = ({ params }: { params: { id: string } }) => { Category filter - - My GP categories ({user?.categories.length}) - + + + + + My GP categories ({user?.categories.length}) + + + +
    + {user?.categories + .map((category) => category.name) + .join(", ")} +
    +
    +
    +
    @@ -139,8 +162,8 @@ const Page = ({ params }: { params: { id: string } }) => { {categories?.map((category) => ( {category.name} diff --git a/frontend/components/navigation/sidebar/sidebar.tsx b/frontend/components/navigation/sidebar/sidebar.tsx index 812ae1fb..385e2577 100644 --- a/frontend/components/navigation/sidebar/sidebar.tsx +++ b/frontend/components/navigation/sidebar/sidebar.tsx @@ -13,11 +13,11 @@ import { NotebookIcon, } from "lucide-react"; +import { Separator } from "@/components/ui/separator"; import { getCategories } from "@/queries/category"; import { useUserStore } from "@/store/user/user-store-provider"; import SidebarItemWithIcon from "./sidebar-item-with-icon"; -import { Separator } from "@/components/ui/separator"; /* Assumption: This component is only rendered if the user is logged in */ const Sidebar = () => { @@ -60,7 +60,7 @@ const Sidebar = () => { path="/ask" />
    - +
    , + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d3b21d18..70e7e7c6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,6 +26,7 @@ "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.3", "@tanstack/react-query": "^5.56.2", "axios": "^1.7.7", "class-variance-authority": "^0.7.0", @@ -2232,6 +2233,130 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", + "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index dafe2b64..76095e74 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.3", "@tanstack/react-query": "^5.56.2", "axios": "^1.7.7", "class-variance-authority": "^0.7.0", From 11fbdc7f66ac775f6f8424e89b3d2dc6b84a53e8 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Wed, 30 Oct 2024 07:28:57 +0800 Subject: [PATCH 27/27] feat: add tooltip --- .../app/(authenticated)/articles/page.tsx | 17 ++++++------ .../(authenticated)/categories/[id]/page.tsx | 13 +++++----- .../components/navigation/sidebar/sidebar.tsx | 16 ------------ frontend/components/ui/tooltip.tsx | 26 +++++++++---------- 4 files changed, 29 insertions(+), 43 deletions(-) diff --git a/frontend/app/(authenticated)/articles/page.tsx b/frontend/app/(authenticated)/articles/page.tsx index a83ae6f0..364f1b29 100644 --- a/frontend/app/(authenticated)/articles/page.tsx +++ b/frontend/app/(authenticated)/articles/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; @@ -17,18 +16,17 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import usePagination from "@/hooks/use-pagination"; -import { getArticlesPage } from "@/queries/article"; -import { getCategories } from "@/queries/category"; -import { useUserStore } from "@/store/user/user-store-provider"; -import { parseDate, toQueryDate } from "@/utils/date"; - import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; +import usePagination from "@/hooks/use-pagination"; +import { getArticlesPage } from "@/queries/article"; +import { getCategories } from "@/queries/category"; +import { useUserStore } from "@/store/user/user-store-provider"; +import { parseDate, toQueryDate } from "@/utils/date"; import ArticlesList from "./articles-list"; @@ -143,7 +141,10 @@ const Articles = () => { { - catId !== categoryId.toString() && + if (catId !== categoryId.toString()) { router.push( catId === "my" ? "/articles" : `/categories/${catId}`, ); + } + return catId; }} > { const pathname = usePathname(); - const { data: categories, isSuccess } = useQuery(getCategories()); - const user = useUserStore((state) => state.user); - const userCategoryIds = user?.categories.map((category) => category.id) || []; - // const otherCategories = categories?.filter( - // (category) => !userCategoryIds.includes(category.id), - // ); - return (
    @@ -87,12 +77,6 @@ const Sidebar = () => { path="/questions" />
    - {isSuccess && ( - <> - {/* - */} - - )}
    ); }; diff --git a/frontend/components/ui/tooltip.tsx b/frontend/components/ui/tooltip.tsx index 30fc44d9..f7975c8c 100644 --- a/frontend/components/ui/tooltip.tsx +++ b/frontend/components/ui/tooltip.tsx @@ -1,30 +1,30 @@ -"use client" +"use client"; -import * as React from "react" -import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const TooltipProvider = TooltipPrimitive.Provider +const TooltipProvider = TooltipPrimitive.Provider; -const Tooltip = TooltipPrimitive.Root +const Tooltip = TooltipPrimitive.Root; -const TooltipTrigger = TooltipPrimitive.Trigger +const TooltipTrigger = TooltipPrimitive.Trigger; const TooltipContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( -)) -TooltipContent.displayName = TooltipPrimitive.Content.displayName +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } +export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };