From 0718e01118f78f3430cc0871acf635e70b5ab1db Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 00:22:49 +0800 Subject: [PATCH 01/28] fix: login user store --- frontend/.env.example | 2 +- frontend/app/login/page.tsx | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/.env.example b/frontend/.env.example index 3304aa4d..a6412939 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,4 +1,4 @@ -NEXT_PUBLIC_APP_NAME=TODO +NEXT_PUBLIC_APP_NAME=Jippy NEXT_PUBLIC_FRONTEND_URL="http://localhost:3000" NEXT_PUBLIC_BACKEND_URL="http://localhost:8000" NEXT_PUBLIC_GOOGLE_CLIENT_ID= \ No newline at end of file diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index d7f81357..d443d244 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -23,6 +23,7 @@ import { CardTitle, } from "@/components/ui/card"; import { Form } from "@/components/ui/form"; +import { useUserStore } from "@/store/user/user-store-provider"; const loginFormSchema = z.object({ email: z.string().email("Invalid email address"), @@ -38,7 +39,9 @@ type LoginForm = z.infer; function LoginPage() { const router = useRouter(); + const setLoggedIn = useUserStore((state) => state.setLoggedIn); const [isError, setIsError] = useState(false); + const form = useForm({ resolver: zodResolver(loginFormSchema), defaultValues: loginFormDefault, @@ -54,6 +57,8 @@ function LoginPage() { setIsError(true); } else { setIsError(false); + const { id: userId, email } = response.data.user; + setLoggedIn(userId, email); router.push("/"); } }; From 4db2a14a64b0cb7a3944c6ff71cf88db83e6a923 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 01:28:43 +0800 Subject: [PATCH 02/28] feat: add 404 page --- frontend/app/not-found.tsx | 29 +++++++++++++++++++++++ frontend/assets/jippy-clown.tsx | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 frontend/app/not-found.tsx create mode 100644 frontend/assets/jippy-clown.tsx diff --git a/frontend/app/not-found.tsx b/frontend/app/not-found.tsx new file mode 100644 index 00000000..d565900e --- /dev/null +++ b/frontend/app/not-found.tsx @@ -0,0 +1,29 @@ +import JippyClownIcon from "@/assets/jippy-clown"; +import { Button } from "@/components/ui/button"; + +function AboutPage() { + return ( +
+
+ +
+

+ 404 Not Found Error +

+

+ Ribbit! Jippy couldn't find that page... +

+
+
+
+

+ The page you were looking for doesn't exist. The page might have + moved, or you might have clicked on a bad link. +

+ +
+
+ ); +} + +export default AboutPage; diff --git a/frontend/assets/jippy-clown.tsx b/frontend/assets/jippy-clown.tsx new file mode 100644 index 00000000..52139a40 --- /dev/null +++ b/frontend/assets/jippy-clown.tsx @@ -0,0 +1,41 @@ +const JippyClownIcon = () => { + return ( + + + + + + + + + + + + + + + + ); +}; + +export default JippyClownIcon; From 568b648e8fa13b161fb430c6889a1aa61ca8b424 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 02:39:10 +0800 Subject: [PATCH 03/28] feat: add jippy assets --- frontend/assets/jippy-icon/jippy-icon-lg.tsx | 33 +++++++++++++++++ frontend/assets/jippy-icon/jippy-icon-md.tsx | 33 +++++++++++++++++ frontend/assets/jippy-icon/jippy-icon-sm.tsx | 33 +++++++++++++++++ frontend/assets/jippy-icon/jippy-icon-xl.tsx | 33 +++++++++++++++++ frontend/assets/jippy-logo/jippy-logo-lg.tsx | 37 +++++++++++++++++++ frontend/assets/jippy-logo/jippy-logo-md.tsx | 37 +++++++++++++++++++ frontend/assets/jippy-logo/jippy-logo-sm.tsx | 37 +++++++++++++++++++ frontend/assets/jippy-logo/jippy-logo-xl.tsx | 37 +++++++++++++++++++ .../components/auth/user-profile-button.tsx | 1 + 9 files changed, 281 insertions(+) create mode 100644 frontend/assets/jippy-icon/jippy-icon-lg.tsx create mode 100644 frontend/assets/jippy-icon/jippy-icon-md.tsx create mode 100644 frontend/assets/jippy-icon/jippy-icon-sm.tsx create mode 100644 frontend/assets/jippy-icon/jippy-icon-xl.tsx create mode 100644 frontend/assets/jippy-logo/jippy-logo-lg.tsx create mode 100644 frontend/assets/jippy-logo/jippy-logo-md.tsx create mode 100644 frontend/assets/jippy-logo/jippy-logo-sm.tsx create mode 100644 frontend/assets/jippy-logo/jippy-logo-xl.tsx diff --git a/frontend/assets/jippy-icon/jippy-icon-lg.tsx b/frontend/assets/jippy-icon/jippy-icon-lg.tsx new file mode 100644 index 00000000..df0876d5 --- /dev/null +++ b/frontend/assets/jippy-icon/jippy-icon-lg.tsx @@ -0,0 +1,33 @@ +const JippyIconLg = ({ classname }: { classname?: string }) => { + return ( + + + + + + + ); +}; + +export default JippyIconLg; diff --git a/frontend/assets/jippy-icon/jippy-icon-md.tsx b/frontend/assets/jippy-icon/jippy-icon-md.tsx new file mode 100644 index 00000000..1d735fb4 --- /dev/null +++ b/frontend/assets/jippy-icon/jippy-icon-md.tsx @@ -0,0 +1,33 @@ +const JippyIconMd = ({ classname }: { classname?: string }) => { + return ( + + + + + + + ); +}; + +export default JippyIconMd; diff --git a/frontend/assets/jippy-icon/jippy-icon-sm.tsx b/frontend/assets/jippy-icon/jippy-icon-sm.tsx new file mode 100644 index 00000000..efe95570 --- /dev/null +++ b/frontend/assets/jippy-icon/jippy-icon-sm.tsx @@ -0,0 +1,33 @@ +const JippyIconSm = ({ classname }: { classname?: string }) => { + return ( + + + + + + + ); +}; + +export default JippyIconSm; diff --git a/frontend/assets/jippy-icon/jippy-icon-xl.tsx b/frontend/assets/jippy-icon/jippy-icon-xl.tsx new file mode 100644 index 00000000..5b389202 --- /dev/null +++ b/frontend/assets/jippy-icon/jippy-icon-xl.tsx @@ -0,0 +1,33 @@ +const JippyIconXl = ({ classname }: { classname?: string }) => { + return ( + + + + + + + ); +}; + +export default JippyIconXl; diff --git a/frontend/assets/jippy-logo/jippy-logo-lg.tsx b/frontend/assets/jippy-logo/jippy-logo-lg.tsx new file mode 100644 index 00000000..b29d1ae5 --- /dev/null +++ b/frontend/assets/jippy-logo/jippy-logo-lg.tsx @@ -0,0 +1,37 @@ +const JippyLogoLg = ({ classname }: { classname?: string }) => { + return ( + + + + + + + + ); +}; + +export default JippyLogoLg; diff --git a/frontend/assets/jippy-logo/jippy-logo-md.tsx b/frontend/assets/jippy-logo/jippy-logo-md.tsx new file mode 100644 index 00000000..8a0e47ff --- /dev/null +++ b/frontend/assets/jippy-logo/jippy-logo-md.tsx @@ -0,0 +1,37 @@ +const JippyLogoMd = ({ classname }: { classname?: string }) => { + return ( + + + + + + + + ); +}; + +export default JippyLogoMd; diff --git a/frontend/assets/jippy-logo/jippy-logo-sm.tsx b/frontend/assets/jippy-logo/jippy-logo-sm.tsx new file mode 100644 index 00000000..a72e68f0 --- /dev/null +++ b/frontend/assets/jippy-logo/jippy-logo-sm.tsx @@ -0,0 +1,37 @@ +const JippyLogoSm = ({ classname }: { classname?: string }) => { + return ( + + + + + + + + ); +}; + +export default JippyLogoSm; diff --git a/frontend/assets/jippy-logo/jippy-logo-xl.tsx b/frontend/assets/jippy-logo/jippy-logo-xl.tsx new file mode 100644 index 00000000..dfceaef2 --- /dev/null +++ b/frontend/assets/jippy-logo/jippy-logo-xl.tsx @@ -0,0 +1,37 @@ +const JippyLogoXl = ({ classname }: { classname?: string }) => { + return ( + + + + + + + + ); +}; + +export default JippyLogoXl; diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index f422e9e7..d74a14fb 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -21,6 +21,7 @@ const UserProfileButton = () => { const signout = async () => { await logoutAuthLogoutGet({ withCredentials: true }); setNotLoggedIn(); + router.push("/"); }; return ( From 63a0a62a2a88301fffca03b80ad397e2df3af493 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 02:39:25 +0800 Subject: [PATCH 04/28] feat: jippy the clown --- frontend/app/not-found.tsx | 4 ++-- frontend/assets/jippy-clown.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/app/not-found.tsx b/frontend/app/not-found.tsx index d565900e..dd7281a5 100644 --- a/frontend/app/not-found.tsx +++ b/frontend/app/not-found.tsx @@ -1,11 +1,11 @@ -import JippyClownIcon from "@/assets/jippy-clown"; +import JippyClown from "@/assets/jippy-clown"; import { Button } from "@/components/ui/button"; function AboutPage() { return (
- +

404 Not Found Error diff --git a/frontend/assets/jippy-clown.tsx b/frontend/assets/jippy-clown.tsx index 52139a40..877a06ac 100644 --- a/frontend/assets/jippy-clown.tsx +++ b/frontend/assets/jippy-clown.tsx @@ -1,4 +1,4 @@ -const JippyClownIcon = () => { +const JippyClown = () => { return ( { ); }; -export default JippyClownIcon; +export default JippyClown; From ee508b56e212a7dc230512b597ce923de08f705f Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 02:39:47 +0800 Subject: [PATCH 05/28] feat: add jippy to navbar --- frontend/components/navigation/navbar.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/components/navigation/navbar.tsx b/frontend/components/navigation/navbar.tsx index 780d53e7..4ca9abff 100644 --- a/frontend/components/navigation/navbar.tsx +++ b/frontend/components/navigation/navbar.tsx @@ -8,6 +8,8 @@ import { } from "@radix-ui/react-navigation-menu"; import { useQuery } from "@tanstack/react-query"; +import JippyIcon from "@/assets/jippy-icon/jippy-icon-sm"; +import JippyLogo from "@/assets/jippy-logo/jippy-logo-sm"; import UserProfileButton from "@/components/auth/user-profile-button"; import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; import { getUserProfile } from "@/queries/user"; @@ -33,12 +35,11 @@ function Navbar() { return (
-
+
- - {process.env.NEXT_PUBLIC_APP_NAME} - + + From 9884dce8bc6883d81c00ffa9a468b3d7f30ab6f5 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 03:21:09 +0800 Subject: [PATCH 06/28] fix: svg asset errors --- frontend/assets/jippy-clown.tsx | 4 ++-- frontend/assets/jippy-icon/jippy-icon-lg.tsx | 4 ++-- frontend/assets/jippy-icon/jippy-icon-md.tsx | 4 ++-- frontend/assets/jippy-icon/jippy-icon-sm.tsx | 4 ++-- frontend/assets/jippy-icon/jippy-icon-xl.tsx | 4 ++-- frontend/assets/jippy-logo/jippy-logo-lg.tsx | 4 ++-- frontend/assets/jippy-logo/jippy-logo-md.tsx | 4 ++-- frontend/assets/jippy-logo/jippy-logo-sm.tsx | 4 ++-- frontend/assets/jippy-logo/jippy-logo-xl.tsx | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/assets/jippy-clown.tsx b/frontend/assets/jippy-clown.tsx index 877a06ac..5e00c423 100644 --- a/frontend/assets/jippy-clown.tsx +++ b/frontend/assets/jippy-clown.tsx @@ -20,8 +20,8 @@ const JippyClown = () => { { { { { { { { { Date: Mon, 23 Sep 2024 03:21:31 +0800 Subject: [PATCH 07/28] feat: add favicon --- frontend/app/favicon.ico | Bin 25931 -> 4950 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/app/favicon.ico b/frontend/app/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..d86998f46e93fb2b2f5164d30b26aaa29a45a644 100644 GIT binary patch literal 4950 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 LrJugUA?W`A8`#=m From 18f75345a2396cf77c2f3cd52dd2c9764389635b Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 05:48:28 +0800 Subject: [PATCH 08/28] feat: redirect authenticated users from auth pages --- frontend/app/login/page.tsx | 5 +++++ frontend/app/register/page.tsx | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index d443d244..c641d63a 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -39,6 +39,7 @@ type LoginForm = z.infer; function LoginPage() { const router = useRouter(); + const isLoggedIn = useUserStore((state) => state.isLoggedIn); const setLoggedIn = useUserStore((state) => state.setLoggedIn); const [isError, setIsError] = useState(false); @@ -63,6 +64,10 @@ function LoginPage() { } }; + if (isLoggedIn) { + router.push("/"); + } + return ( diff --git a/frontend/app/register/page.tsx b/frontend/app/register/page.tsx index 86c97c06..2267f77c 100644 --- a/frontend/app/register/page.tsx +++ b/frontend/app/register/page.tsx @@ -16,6 +16,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert"; import { Box } from "@/components/ui/box"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form"; +import { useUserStore } from "@/store/user/user-store-provider"; const registerFormSchema = z.object({ email: z.string().email("Invalid email address"), @@ -31,6 +32,7 @@ type RegisterForm = z.infer; function RegisterPage() { const router = useRouter(); + const isLoggedIn = useUserStore((state) => state.isLoggedIn); const [isError, setIsError] = useState(false); const form = useForm({ resolver: zodResolver(registerFormSchema), @@ -51,6 +53,10 @@ function RegisterPage() { } }; + if (isLoggedIn) { + router.push("/"); + } + return ( From 3a17edeca0f6507f7a22bd2c6789922c38e07949 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 05:48:40 +0800 Subject: [PATCH 09/28] feat: add chip component --- frontend/components/display/chip.tsx | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 frontend/components/display/chip.tsx diff --git a/frontend/components/display/chip.tsx b/frontend/components/display/chip.tsx new file mode 100644 index 00000000..09d88d86 --- /dev/null +++ b/frontend/components/display/chip.tsx @@ -0,0 +1,39 @@ +import { Box } from "@/components/ui/box"; +import { cn } from "@/lib/utils"; +import { cva, VariantProps } from "class-variance-authority"; + +const chipVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-xs font-medium transition-colors disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-sky-200/60 text-sky-600 hover:bg-sky-200/40", + destructive: "bg-red-200/60 text-destructive/90 hover:bg-red-200/40", + }, + size: { + default: "h-6 px-2 py-2", + sm: "h-5 rounded-md px-2", + lg: "h-7 rounded-md px-3 text-sm", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +interface ChipProps extends VariantProps { + label: string; + className: string; +} + +const Chip = ({ label, variant, size, className }: ChipProps) => { + return ( + + {label} + + ); +}; + +export default Chip; From d4a7eea839d0ea15b23185cb61162b892a07bb54 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 05:49:12 +0800 Subject: [PATCH 10/28] feat: update user profile button --- .../components/auth/user-profile-button.tsx | 53 ++++++++++++++----- frontend/components/ui/avatar.tsx | 4 +- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index d74a14fb..e00faa43 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -1,6 +1,15 @@ +import { createElement, useState } from "react"; import { useRouter } from "next/navigation"; +import { + ChevronsDownUpIcon, + ChevronsUpDownIcon, + CreditCardIcon, + LogOutIcon, + UserIcon, +} from "lucide-react"; import { logoutAuthLogoutGet } from "@/client"; +import Chip from "@/components/display/chip"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { DropdownMenu, @@ -15,6 +24,7 @@ import { useUserStore } from "@/store/user/user-store-provider"; import { getInitialFromEmail, getNameFromEmail } from "@/utils/string"; const UserProfileButton = () => { + const [isOpen, setIsOpen] = useState(false); const { email, setNotLoggedIn } = useUserStore((state) => state); const router = useRouter(); @@ -26,24 +36,40 @@ const UserProfileButton = () => { return (
- + setIsOpen(isOpen)}> - - {getInitialFromEmail(email)} - +
+
+ + {getInitialFromEmail(email)} + +
+

{getNameFromEmail(email)}

+

{email}

+
+
+ {createElement(isOpen ? ChevronsDownUpIcon : ChevronsUpDownIcon, { + className: "h-4 w-4 ml-auto shrink-0 opacity-50", + })} +
- - -
-

{getNameFromEmail(email)}

-

{email}

-
-
+ + My account router.push("/user/profile")}> - Manage my profile + + Profile settings + + router.push("/user/profile")}> + + Manage billings + @@ -52,7 +78,8 @@ const UserProfileButton = () => { className="text-destructive focus:bg-destructive/10 focus:text-destructive" onClick={signout} > - Log out + + Log out diff --git a/frontend/components/ui/avatar.tsx b/frontend/components/ui/avatar.tsx index 13fb9acd..a61bd2fa 100644 --- a/frontend/components/ui/avatar.tsx +++ b/frontend/components/ui/avatar.tsx @@ -11,7 +11,7 @@ const Avatar = React.forwardRef< >(({ className, ...props }, ref) => ( (({ className, ...props }, ref) => ( Date: Mon, 23 Sep 2024 06:01:58 +0800 Subject: [PATCH 11/28] feat: initial sidebar scaffolding --- frontend/components/layout/app-layout.tsx | 14 +- frontend/components/navigation/navbar.tsx | 10 +- frontend/components/navigation/sidebar.tsx | 13 ++ frontend/components/ui/select.tsx | 160 +++++++++++++++++++++ frontend/package-lock.json | 50 +++++++ frontend/package.json | 1 + 6 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 frontend/components/navigation/sidebar.tsx create mode 100644 frontend/components/ui/select.tsx diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index 15402218..0a3add2f 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -4,12 +4,15 @@ import { ReactNode, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import Navbar from "@/components/navigation/navbar"; +import Sidebar from "@/components/navigation/sidebar"; import { Toaster } from "@/components/ui/toaster"; import { getUserProfile } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; const AppLayout = ({ children }: { children: ReactNode }) => { - const { setLoggedIn, setNotLoggedIn } = useUserStore((state) => state); + const { isLoggedIn, setLoggedIn, setNotLoggedIn } = useUserStore( + (state) => state, + ); const { data: userProfile, isSuccess: isUserProfileSuccess } = useQuery(getUserProfile()); @@ -24,7 +27,14 @@ const AppLayout = ({ children }: { children: ReactNode }) => { return (
-
{children}
+
+ {isLoggedIn && } +
+ {children} +
+
); diff --git a/frontend/components/navigation/navbar.tsx b/frontend/components/navigation/navbar.tsx index 4ca9abff..d26483b7 100644 --- a/frontend/components/navigation/navbar.tsx +++ b/frontend/components/navigation/navbar.tsx @@ -10,7 +10,6 @@ import { useQuery } from "@tanstack/react-query"; import JippyIcon from "@/assets/jippy-icon/jippy-icon-sm"; import JippyLogo from "@/assets/jippy-logo/jippy-logo-sm"; -import UserProfileButton from "@/components/auth/user-profile-button"; import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; import { getUserProfile } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; @@ -19,9 +18,7 @@ import { NavItem } from "@/types/navigation"; export const NavItems: NavItem[] = []; function Navbar() { - const { isLoggedIn, setLoggedIn, setNotLoggedIn } = useUserStore( - (state) => state, - ); + const { setLoggedIn, setNotLoggedIn } = useUserStore((state) => state); const { data: userProfile, isSuccess: isUserProfileSuccess } = useQuery(getUserProfile()); @@ -34,8 +31,8 @@ function Navbar() { }, [userProfile, isUserProfileSuccess, setLoggedIn, setNotLoggedIn]); return ( -
-
+
+
@@ -57,7 +54,6 @@ function Navbar() {
- {isLoggedIn && }
); diff --git a/frontend/components/navigation/sidebar.tsx b/frontend/components/navigation/sidebar.tsx new file mode 100644 index 00000000..260e6240 --- /dev/null +++ b/frontend/components/navigation/sidebar.tsx @@ -0,0 +1,13 @@ +import UserProfileButton from "@/components/auth/user-profile-button"; +import { useUserStore } from "@/store/user/user-store-provider"; + +const Sidebar = () => { + const isLoggedIn = useUserStore((state) => state.isLoggedIn); + return ( +
+ {isLoggedIn && } +
+ ); +}; + +export default Sidebar; diff --git a/frontend/components/ui/select.tsx b/frontend/components/ui/select.tsx new file mode 100644 index 00000000..91566d73 --- /dev/null +++ b/frontend/components/ui/select.tsx @@ -0,0 +1,160 @@ +"use client"; + +import * as React from "react"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className, + )} + ref={ref} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +}; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9a173be6..572ef4fb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@tanstack/react-query": "^5.56.2", @@ -1059,6 +1060,12 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", @@ -1599,6 +1606,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", + "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "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-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index cfaefd2b..9c1e6990 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.0", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@tanstack/react-query": "^5.56.2", From e819f559867e70529430e5bfc1574c46b315d0ed Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 07:31:33 +0800 Subject: [PATCH 12/28] feat: make sidebar resizable --- frontend/components/layout/app-layout.tsx | 85 ++++++++++++++++++-- frontend/components/navigation/sidebar.tsx | 2 +- frontend/components/ui/resizable.tsx | 45 +++++++++++ frontend/hooks/use-breakpoint-media-query.ts | 18 +++++ frontend/package-lock.json | 33 ++++++++ frontend/package.json | 2 + frontend/utils/media.ts | 7 ++ 7 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 frontend/components/ui/resizable.tsx create mode 100644 frontend/hooks/use-breakpoint-media-query.ts create mode 100644 frontend/utils/media.ts diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index 0a3add2f..c44155d1 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -1,15 +1,65 @@ "use client"; -import { ReactNode, useEffect } from "react"; +import { ComponentProps, ReactNode, useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import Navbar from "@/components/navigation/navbar"; import Sidebar from "@/components/navigation/sidebar"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable"; import { Toaster } from "@/components/ui/toaster"; import { getUserProfile } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; +import { MediaBreakpoint } from "@/utils/media"; +import useBreakpointMediaQuery from "@/hooks/use-breakpoint-media-query"; + +const breakpointConfigMap: Record< + MediaBreakpoint, + ComponentProps +> = { + [MediaBreakpoint.Sm]: { + defaultSize: 30, + maxSize: 35, + minSize: 10, + collapsible: true, + collapsedSize: 1, + }, + [MediaBreakpoint.Md]: { + defaultSize: 25, + maxSize: 40, + minSize: 20, + collapsible: true, + collapsedSize: 1, + }, + [MediaBreakpoint.Lg]: { + defaultSize: 25, + maxSize: 40, + minSize: 20, + collapsible: true, + collapsedSize: 1, + }, + [MediaBreakpoint.Xl]: { + defaultSize: 25, + maxSize: 40, + minSize: 15, + collapsible: true, + collapsedSize: 1, + }, + [MediaBreakpoint.XXl]: { + defaultSize: 25, + maxSize: 40, + minSize: 15, + collapsible: true, + collapsedSize: 1, + }, +}; const AppLayout = ({ children }: { children: ReactNode }) => { + const mediaBreakpoint = useBreakpointMediaQuery(); + const [isCollapsed, setIsCollapsed] = useState(false); const { isLoggedIn, setLoggedIn, setNotLoggedIn } = useUserStore( (state) => state, ); @@ -27,13 +77,32 @@ const AppLayout = ({ children }: { children: ReactNode }) => { return (
-
- {isLoggedIn && } -
- {children} -
+
+ + {isLoggedIn && ( + <> + setIsCollapsed(true)} + onExpand={() => setIsCollapsed(false)} + {...breakpointConfigMap[mediaBreakpoint]} + > + + + + + )} + +
+ {children} +
+
+
diff --git a/frontend/components/navigation/sidebar.tsx b/frontend/components/navigation/sidebar.tsx index 260e6240..7049f0fa 100644 --- a/frontend/components/navigation/sidebar.tsx +++ b/frontend/components/navigation/sidebar.tsx @@ -4,7 +4,7 @@ import { useUserStore } from "@/store/user/user-store-provider"; const Sidebar = () => { const isLoggedIn = useUserStore((state) => state.isLoggedIn); return ( -
+
{isLoggedIn && }
); diff --git a/frontend/components/ui/resizable.tsx b/frontend/components/ui/resizable.tsx new file mode 100644 index 00000000..f4bc5586 --- /dev/null +++ b/frontend/components/ui/resizable.tsx @@ -0,0 +1,45 @@ +"use client" + +import { GripVertical } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +const ResizablePanelGroup = ({ + className, + ...props +}: React.ComponentProps) => ( + +) + +const ResizablePanel = ResizablePrimitive.Panel + +const ResizableHandle = ({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) => ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+) + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/frontend/hooks/use-breakpoint-media-query.ts b/frontend/hooks/use-breakpoint-media-query.ts new file mode 100644 index 00000000..f018757f --- /dev/null +++ b/frontend/hooks/use-breakpoint-media-query.ts @@ -0,0 +1,18 @@ +import { MediaBreakpoint } from "@/utils/media"; +import { useMediaQuery } from "usehooks-ts"; + +function useBreakpointMediaQuery(): MediaBreakpoint { + const isMdBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Md})`); + const isLgBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Lg})`); + const isXlBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Xl})`); + const isXxlBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.XXl})`); + + if (isXxlBreakpoint) return MediaBreakpoint.XXl; + if (isXlBreakpoint) return MediaBreakpoint.Xl; + if (isMdBreakpoint) return MediaBreakpoint.Md; + if (isLgBreakpoint) return MediaBreakpoint.Lg; + + return MediaBreakpoint.Sm; +} + +export default useBreakpointMediaQuery; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 572ef4fb..e1378e61 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,8 +27,10 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.53.0", + "react-resizable-panels": "^2.1.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", + "usehooks-ts": "^3.1.0", "zod": "^3.23.8", "zustand": "^5.0.0-rc.2" }, @@ -5369,6 +5371,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6566,6 +6574,16 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.3.tgz", + "integrity": "sha512-Zz0sCro6aUubL+hYh67eTnn5vxAu+HUZ7+IXvGjsBCBaudDEpIyZyDGE3vcgKi2w6IN3rYH+WXO+MwpgMSOpaQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -7707,6 +7725,21 @@ } } }, + "node_modules/usehooks-ts": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz", + "integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9c1e6990..787fe9d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,8 +29,10 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.53.0", + "react-resizable-panels": "^2.1.3", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", + "usehooks-ts": "^3.1.0", "zod": "^3.23.8", "zustand": "^5.0.0-rc.2" }, diff --git a/frontend/utils/media.ts b/frontend/utils/media.ts new file mode 100644 index 00000000..0100013a --- /dev/null +++ b/frontend/utils/media.ts @@ -0,0 +1,7 @@ +export enum MediaBreakpoint { + Sm = "640px", + Md = "768px", + Lg = "1024px", + Xl = "1280px", + XXl = "1536px", +} From d47f479feefb03dadafdf67d595ad46f09042961 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 07:38:40 +0800 Subject: [PATCH 13/28] feat: add unauth navbar --- frontend/components/navigation/navbar.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/components/navigation/navbar.tsx b/frontend/components/navigation/navbar.tsx index d26483b7..f6e1576f 100644 --- a/frontend/components/navigation/navbar.tsx +++ b/frontend/components/navigation/navbar.tsx @@ -1,5 +1,4 @@ import { useEffect } from "react"; -import Link from "next/link"; import { NavigationMenu, NavigationMenuItem, @@ -14,11 +13,15 @@ import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; import { getUserProfile } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; import { NavItem } from "@/types/navigation"; +import { Button } from "../ui/button"; +import Link from "./link"; export const NavItems: NavItem[] = []; function Navbar() { - const { setLoggedIn, setNotLoggedIn } = useUserStore((state) => state); + const { isLoggedIn, setLoggedIn, setNotLoggedIn } = useUserStore( + (state) => state, + ); const { data: userProfile, isSuccess: isUserProfileSuccess } = useQuery(getUserProfile()); @@ -54,6 +57,20 @@ function Navbar() {
+ {!isLoggedIn && ( +
+ +
+ )}
); From 7bb2d04d13eeb249501b74dcdd02d4c9b144d3bf Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 07:40:37 +0800 Subject: [PATCH 14/28] fix: remove extranous panel border --- frontend/components/layout/app-layout.tsx | 4 ++-- frontend/components/navigation/sidebar.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index c44155d1..c469ec40 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -43,14 +43,14 @@ const breakpointConfigMap: Record< }, [MediaBreakpoint.Xl]: { defaultSize: 25, - maxSize: 40, + maxSize: 35, minSize: 15, collapsible: true, collapsedSize: 1, }, [MediaBreakpoint.XXl]: { defaultSize: 25, - maxSize: 40, + maxSize: 35, minSize: 15, collapsible: true, collapsedSize: 1, diff --git a/frontend/components/navigation/sidebar.tsx b/frontend/components/navigation/sidebar.tsx index 7049f0fa..04ff82c8 100644 --- a/frontend/components/navigation/sidebar.tsx +++ b/frontend/components/navigation/sidebar.tsx @@ -4,7 +4,7 @@ import { useUserStore } from "@/store/user/user-store-provider"; const Sidebar = () => { const isLoggedIn = useUserStore((state) => state.isLoggedIn); return ( -
+
{isLoggedIn && }
); From fe5bbd834736eaf61bb63a8711c90c1b1de503f9 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 07:52:19 +0800 Subject: [PATCH 15/28] fix: user profile button hoverable --- frontend/components/auth/user-profile-button.tsx | 4 ++-- frontend/components/layout/app-layout.tsx | 2 +- frontend/components/navigation/sidebar.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index e00faa43..1087ee11 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -35,10 +35,10 @@ const UserProfileButton = () => { }; return ( -
+
setIsOpen(isOpen)}> -
+
{getInitialFromEmail(email)} diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index c469ec40..7d45caff 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -42,7 +42,7 @@ const breakpointConfigMap: Record< collapsedSize: 1, }, [MediaBreakpoint.Xl]: { - defaultSize: 25, + defaultSize: 20, maxSize: 35, minSize: 15, collapsible: true, diff --git a/frontend/components/navigation/sidebar.tsx b/frontend/components/navigation/sidebar.tsx index 04ff82c8..85146a40 100644 --- a/frontend/components/navigation/sidebar.tsx +++ b/frontend/components/navigation/sidebar.tsx @@ -4,7 +4,7 @@ import { useUserStore } from "@/store/user/user-store-provider"; const Sidebar = () => { const isLoggedIn = useUserStore((state) => state.isLoggedIn); return ( -
+
{isLoggedIn && }
); From b9f527a882317a63c362bc8a78a5a1d5043281a4 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 08:12:05 +0800 Subject: [PATCH 16/28] feat: regenerate client --- frontend/client/core/utils.ts | 2 +- frontend/client/schemas.gen.ts | 288 +++++++++++++++++++++++++++++++- frontend/client/services.gen.ts | 140 ++++++++++++++++ frontend/client/types.gen.ts | 132 +++++++++++++++ 4 files changed, 560 insertions(+), 2 deletions(-) diff --git a/frontend/client/core/utils.ts b/frontend/client/core/utils.ts index 309135e0..f7103fc4 100644 --- a/frontend/client/core/utils.ts +++ b/frontend/client/core/utils.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Config } from "./types"; interface PathSerializer { diff --git a/frontend/client/schemas.gen.ts b/frontend/client/schemas.gen.ts index e8916813..a4999a29 100644 --- a/frontend/client/schemas.gen.ts +++ b/frontend/client/schemas.gen.ts @@ -1,5 +1,43 @@ // This file is auto-generated by @hey-api/openapi-ts +export const AnalysisDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + category: { + $ref: "#/components/schemas/CategoryDTO", + }, + content: { + type: "string", + title: "Content", + }, + }, + type: "object", + required: ["id", "category", "content"], + title: "AnalysisDTO", +} as const; + +export const AnswerDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + points: { + items: { + $ref: "#/components/schemas/PointMiniDTO", + }, + type: "array", + title: "Points", + }, + }, + type: "object", + required: ["id", "points"], + title: "AnswerDTO", +} as const; + export const Body_log_in_auth_login_postSchema = { properties: { grant_type: { @@ -55,6 +93,143 @@ export const Body_log_in_auth_login_postSchema = { title: "Body_log_in_auth_login_post", } as const; +export const CategoryDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + name: { + type: "string", + title: "Name", + }, + }, + type: "object", + required: ["id", "name"], + title: "CategoryDTO", +} as const; + +export const CreateUserQuestionSchema = { + properties: { + question: { + type: "string", + title: "Question", + }, + }, + type: "object", + required: ["question"], + title: "CreateUserQuestion", +} as const; + +export const EventDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + title: { + type: "string", + title: "Title", + }, + description: { + type: "string", + title: "Description", + }, + is_singapore: { + type: "boolean", + title: "Is Singapore", + }, + date: { + type: "string", + format: "date-time", + title: "Date", + }, + categories: { + items: { + $ref: "#/components/schemas/CategoryDTO", + }, + type: "array", + title: "Categories", + }, + analysises: { + items: { + $ref: "#/components/schemas/AnalysisDTO", + }, + type: "array", + title: "Analysises", + }, + gp_questions: { + items: { + $ref: "#/components/schemas/GPQuestionDTO", + }, + type: "array", + title: "Gp Questions", + }, + }, + type: "object", + required: [ + "id", + "title", + "description", + "is_singapore", + "date", + "categories", + "analysises", + "gp_questions", + ], + title: "EventDTO", +} as const; + +export const EventIndexResponseSchema = { + properties: { + total_count: { + type: "integer", + title: "Total Count", + }, + count: { + type: "integer", + title: "Count", + }, + data: { + items: { + $ref: "#/components/schemas/MiniEventDTO", + }, + type: "array", + title: "Data", + }, + }, + type: "object", + required: ["total_count", "count", "data"], + title: "EventIndexResponse", +} as const; + +export const GPQuestionDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + question: { + type: "string", + title: "Question", + }, + is_llm_generated: { + type: "boolean", + title: "Is Llm Generated", + }, + categories: { + items: { + $ref: "#/components/schemas/CategoryDTO", + }, + type: "array", + title: "Categories", + }, + }, + type: "object", + required: ["id", "question", "is_llm_generated", "categories"], + title: "GPQuestionDTO", +} as const; + export const HTTPValidationErrorSchema = { properties: { detail: { @@ -69,6 +244,91 @@ export const HTTPValidationErrorSchema = { title: "HTTPValidationError", } as const; +export const MiniEventDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + title: { + type: "string", + title: "Title", + }, + description: { + type: "string", + title: "Description", + }, + is_singapore: { + type: "boolean", + title: "Is Singapore", + }, + date: { + type: "string", + format: "date-time", + title: "Date", + }, + categories: { + items: { + $ref: "#/components/schemas/CategoryDTO", + }, + type: "array", + title: "Categories", + }, + }, + type: "object", + required: [ + "id", + "title", + "description", + "is_singapore", + "date", + "categories", + ], + title: "MiniEventDTO", +} as const; + +export const PointMiniDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + title: { + type: "string", + title: "Title", + }, + body: { + type: "string", + title: "Body", + }, + events: { + items: { + $ref: "#/components/schemas/EventDTO", + }, + type: "array", + title: "Events", + }, + }, + type: "object", + required: ["id", "title", "body", "events"], + title: "PointMiniDTO", +} as const; + +export const ProfileUpdateSchema = { + properties: { + category_ids: { + items: { + type: "integer", + }, + type: "array", + title: "Category Ids", + }, + }, + type: "object", + required: ["category_ids"], + title: "ProfileUpdate", +} as const; + export const SignUpDataSchema = { properties: { email: { @@ -117,12 +377,38 @@ export const UserPublicSchema = { format: "email", title: "Email", }, + categories: { + items: { + $ref: "#/components/schemas/CategoryDTO", + }, + type: "array", + title: "Categories", + }, }, type: "object", - required: ["id", "email"], + required: ["id", "email", "categories"], title: "UserPublic", } as const; +export const UserQuestionMiniDTOSchema = { + properties: { + id: { + type: "integer", + title: "Id", + }, + question: { + type: "string", + title: "Question", + }, + answer: { + $ref: "#/components/schemas/AnswerDTO", + }, + }, + type: "object", + required: ["id", "question", "answer"], + title: "UserQuestionMiniDTO", +} as const; + export const ValidationErrorSchema = { properties: { loc: { diff --git a/frontend/client/services.gen.ts b/frontend/client/services.gen.ts index f65c9cd9..39abc72b 100644 --- a/frontend/client/services.gen.ts +++ b/frontend/client/services.gen.ts @@ -10,9 +10,26 @@ import type { AuthGoogleAuthGoogleGetData, AuthGoogleAuthGoogleGetError, AuthGoogleAuthGoogleGetResponse, + CreateUserQuestionUserQuestionsPostData, + CreateUserQuestionUserQuestionsPostError, + CreateUserQuestionUserQuestionsPostResponse, + GetCategoriesCategoriesGetError, + GetCategoriesCategoriesGetResponse, + GetEventEventsIdGetData, + GetEventEventsIdGetError, + GetEventEventsIdGetResponse, + GetEventsEventsGetData, + GetEventsEventsGetError, + GetEventsEventsGetResponse, GetUserAuthSessionGetData, GetUserAuthSessionGetError, GetUserAuthSessionGetResponse, + GetUserQuestionsUserQuestionsGetData, + GetUserQuestionsUserQuestionsGetError, + GetUserQuestionsUserQuestionsGetResponse, + GetUserQuestionUserQuestionsIdGetData, + GetUserQuestionUserQuestionsIdGetError, + GetUserQuestionUserQuestionsIdGetResponse, LogInAuthLoginPostData, LogInAuthLoginPostError, LogInAuthLoginPostResponse, @@ -23,6 +40,9 @@ import type { SignUpAuthSignupPostData, SignUpAuthSignupPostError, SignUpAuthSignupPostResponse, + UpdateProfileProfilePutData, + UpdateProfileProfilePutError, + UpdateProfileProfilePutResponse, } from "./types.gen"; export const client = createClient(createConfig()); @@ -129,3 +149,123 @@ export const logoutAuthLogoutGet = ( url: "/auth/logout", }); }; + +/** + * Get Categories + */ +export const getCategoriesCategoriesGet = < + ThrowOnError extends boolean = false, +>( + options?: Options, +) => { + return (options?.client ?? client).get< + GetCategoriesCategoriesGetResponse, + GetCategoriesCategoriesGetError, + ThrowOnError + >({ + ...options, + url: "/categories/", + }); +}; + +/** + * Update Profile + */ +export const updateProfileProfilePut = ( + options: Options, +) => { + return (options?.client ?? client).put< + UpdateProfileProfilePutResponse, + UpdateProfileProfilePutError, + ThrowOnError + >({ + ...options, + url: "/profile/", + }); +}; + +/** + * Get Events + */ +export const getEventsEventsGet = ( + options?: Options, +) => { + return (options?.client ?? client).get< + GetEventsEventsGetResponse, + GetEventsEventsGetError, + ThrowOnError + >({ + ...options, + url: "/events/", + }); +}; + +/** + * Get Event + */ +export const getEventEventsIdGet = ( + options: Options, +) => { + return (options?.client ?? client).get< + GetEventEventsIdGetResponse, + GetEventEventsIdGetError, + ThrowOnError + >({ + ...options, + url: "/events/:id", + }); +}; + +/** + * Get User Questions + */ +export const getUserQuestionsUserQuestionsGet = < + ThrowOnError extends boolean = false, +>( + options?: Options, +) => { + return (options?.client ?? client).get< + GetUserQuestionsUserQuestionsGetResponse, + GetUserQuestionsUserQuestionsGetError, + ThrowOnError + >({ + ...options, + url: "/user-questions/", + }); +}; + +/** + * Create User Question + */ +export const createUserQuestionUserQuestionsPost = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => { + return (options?.client ?? client).post< + CreateUserQuestionUserQuestionsPostResponse, + CreateUserQuestionUserQuestionsPostError, + ThrowOnError + >({ + ...options, + url: "/user-questions/", + }); +}; + +/** + * Get User Question + */ +export const getUserQuestionUserQuestionsIdGet = < + ThrowOnError extends boolean = false, +>( + options: Options, +) => { + return (options?.client ?? client).get< + GetUserQuestionUserQuestionsIdGetResponse, + GetUserQuestionUserQuestionsIdGetError, + ThrowOnError + >({ + ...options, + url: "/user-questions/:id", + }); +}; diff --git a/frontend/client/types.gen.ts b/frontend/client/types.gen.ts index a4e57709..85a6f1a4 100644 --- a/frontend/client/types.gen.ts +++ b/frontend/client/types.gen.ts @@ -1,5 +1,16 @@ // This file is auto-generated by @hey-api/openapi-ts +export type AnalysisDTO = { + id: number; + category: CategoryDTO; + content: string; +}; + +export type AnswerDTO = { + id: number; + points: Array; +}; + export type Body_log_in_auth_login_post = { grant_type?: string | null; username: string; @@ -9,10 +20,63 @@ export type Body_log_in_auth_login_post = { client_secret?: string | null; }; +export type CategoryDTO = { + id: number; + name: string; +}; + +export type CreateUserQuestion = { + question: string; +}; + +export type EventDTO = { + id: number; + title: string; + description: string; + is_singapore: boolean; + date: string; + categories: Array; + analysises: Array; + gp_questions: Array; +}; + +export type EventIndexResponse = { + total_count: number; + count: number; + data: Array; +}; + +export type GPQuestionDTO = { + id: number; + question: string; + is_llm_generated: boolean; + categories: Array; +}; + export type HTTPValidationError = { detail?: Array; }; +export type MiniEventDTO = { + id: number; + title: string; + description: string; + is_singapore: boolean; + date: string; + categories: Array; +}; + +export type PointMiniDTO = { + id: number; + title: string; + body: string; + events: Array; +}; + +export type ProfileUpdate = { + category_ids: Array; +}; + export type SignUpData = { email: string; password: string; @@ -27,6 +91,13 @@ export type Token = { export type UserPublic = { id: number; email: string; + categories: Array; +}; + +export type UserQuestionMiniDTO = { + id: number; + question: string; + answer: AnswerDTO; }; export type ValidationError = { @@ -74,3 +145,64 @@ export type GetUserAuthSessionGetError = HTTPValidationError; export type LogoutAuthLogoutGetResponse = unknown; export type LogoutAuthLogoutGetError = unknown; + +export type GetCategoriesCategoriesGetResponse = Array; + +export type GetCategoriesCategoriesGetError = unknown; + +export type UpdateProfileProfilePutData = { + body: ProfileUpdate; +}; + +export type UpdateProfileProfilePutResponse = UserPublic; + +export type UpdateProfileProfilePutError = HTTPValidationError; + +export type GetEventsEventsGetData = { + query?: { + category_ids?: Array | null; + end_date?: string | null; + limit?: number | null; + offset?: number | null; + start_date?: string | null; + }; +}; + +export type GetEventsEventsGetResponse = EventIndexResponse; + +export type GetEventsEventsGetError = HTTPValidationError; + +export type GetEventEventsIdGetData = { + query: { + id: number; + }; +}; + +export type GetEventEventsIdGetResponse = EventDTO; + +export type GetEventEventsIdGetError = HTTPValidationError; + +export type GetUserQuestionsUserQuestionsGetData = unknown; + +export type GetUserQuestionsUserQuestionsGetResponse = + Array; + +export type GetUserQuestionsUserQuestionsGetError = HTTPValidationError; + +export type CreateUserQuestionUserQuestionsPostData = { + body: CreateUserQuestion; +}; + +export type CreateUserQuestionUserQuestionsPostResponse = UserQuestionMiniDTO; + +export type CreateUserQuestionUserQuestionsPostError = HTTPValidationError; + +export type GetUserQuestionUserQuestionsIdGetData = { + query: { + id: number; + }; +}; + +export type GetUserQuestionUserQuestionsIdGetResponse = unknown; + +export type GetUserQuestionUserQuestionsIdGetError = HTTPValidationError; From f1b637e42b4596cda238cab8e9cc62d9369a8cbc Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 08:12:22 +0800 Subject: [PATCH 17/28] style: fix linter errors --- frontend/components/display/chip.tsx | 3 ++- frontend/components/layout/app-layout.tsx | 8 ++++---- frontend/components/navigation/navbar.tsx | 3 ++- frontend/components/navigation/{ => sidebar}/sidebar.tsx | 5 ++--- frontend/hooks/use-breakpoint-media-query.ts | 3 ++- 5 files changed, 12 insertions(+), 10 deletions(-) rename frontend/components/navigation/{ => sidebar}/sidebar.tsx (58%) diff --git a/frontend/components/display/chip.tsx b/frontend/components/display/chip.tsx index 09d88d86..bf2759f4 100644 --- a/frontend/components/display/chip.tsx +++ b/frontend/components/display/chip.tsx @@ -1,6 +1,7 @@ +import { cva, VariantProps } from "class-variance-authority"; + import { Box } from "@/components/ui/box"; import { cn } from "@/lib/utils"; -import { cva, VariantProps } from "class-variance-authority"; const chipVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-xs font-medium transition-colors disabled:pointer-events-none disabled:opacity-50", diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index 7d45caff..997493f7 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -4,17 +4,17 @@ import { ComponentProps, ReactNode, useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import Navbar from "@/components/navigation/navbar"; -import Sidebar from "@/components/navigation/sidebar"; +import Sidebar from "@/components/navigation/sidebar/sidebar"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Toaster } from "@/components/ui/toaster"; +import useBreakpointMediaQuery from "@/hooks/use-breakpoint-media-query"; import { getUserProfile } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; import { MediaBreakpoint } from "@/utils/media"; -import useBreakpointMediaQuery from "@/hooks/use-breakpoint-media-query"; const breakpointConfigMap: Record< MediaBreakpoint, @@ -85,11 +85,11 @@ const AppLayout = ({ children }: { children: ReactNode }) => { {isLoggedIn && ( <> setIsCollapsed(true)} onExpand={() => setIsCollapsed(false)} + order={1} {...breakpointConfigMap[mediaBreakpoint]} > diff --git a/frontend/components/navigation/navbar.tsx b/frontend/components/navigation/navbar.tsx index f6e1576f..2085e657 100644 --- a/frontend/components/navigation/navbar.tsx +++ b/frontend/components/navigation/navbar.tsx @@ -9,11 +9,12 @@ import { useQuery } from "@tanstack/react-query"; import JippyIcon from "@/assets/jippy-icon/jippy-icon-sm"; import JippyLogo from "@/assets/jippy-logo/jippy-logo-sm"; +import { Button } from "@/components/ui/button"; import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; import { getUserProfile } from "@/queries/user"; import { useUserStore } from "@/store/user/user-store-provider"; import { NavItem } from "@/types/navigation"; -import { Button } from "../ui/button"; + import Link from "./link"; export const NavItems: NavItem[] = []; diff --git a/frontend/components/navigation/sidebar.tsx b/frontend/components/navigation/sidebar/sidebar.tsx similarity index 58% rename from frontend/components/navigation/sidebar.tsx rename to frontend/components/navigation/sidebar/sidebar.tsx index 85146a40..04bcbe3a 100644 --- a/frontend/components/navigation/sidebar.tsx +++ b/frontend/components/navigation/sidebar/sidebar.tsx @@ -1,11 +1,10 @@ import UserProfileButton from "@/components/auth/user-profile-button"; -import { useUserStore } from "@/store/user/user-store-provider"; +/* Assumption: This component is only rendered if the user is logged in */ const Sidebar = () => { - const isLoggedIn = useUserStore((state) => state.isLoggedIn); return (
- {isLoggedIn && } +
); }; diff --git a/frontend/hooks/use-breakpoint-media-query.ts b/frontend/hooks/use-breakpoint-media-query.ts index f018757f..be45caeb 100644 --- a/frontend/hooks/use-breakpoint-media-query.ts +++ b/frontend/hooks/use-breakpoint-media-query.ts @@ -1,6 +1,7 @@ -import { MediaBreakpoint } from "@/utils/media"; import { useMediaQuery } from "usehooks-ts"; +import { MediaBreakpoint } from "@/utils/media"; + function useBreakpointMediaQuery(): MediaBreakpoint { const isMdBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Md})`); const isLgBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Lg})`); From 4f4c7762f0700caef3afb69d39bf6cde4691afd8 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Mon, 23 Sep 2024 15:44:24 +0800 Subject: [PATCH 18/28] feat: add topics sidebar --- .../sidebar/sidebar-item-with-icon.tsx | 23 +++++++++ .../sidebar/sidebar-other-topics.tsx | 50 +++++++++++++++++++ .../components/navigation/sidebar/sidebar.tsx | 4 +- frontend/tailwind.config.ts | 1 + frontend/types/categories.ts | 12 +++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx create mode 100644 frontend/components/navigation/sidebar/sidebar-other-topics.tsx create mode 100644 frontend/types/categories.ts diff --git a/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx b/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx new file mode 100644 index 00000000..ab7d614c --- /dev/null +++ b/frontend/components/navigation/sidebar/sidebar-item-with-icon.tsx @@ -0,0 +1,23 @@ +import { LucideIcon } from "lucide-react"; + +interface SidebarItemWithIconProps { + Icon: LucideIcon; + label: string; +} + +const SidebarItemWithIcon = ({ Icon, label }: SidebarItemWithIconProps) => { + return ( +
+ + + {label} + +
+ ); +}; + +export default SidebarItemWithIcon; diff --git a/frontend/components/navigation/sidebar/sidebar-other-topics.tsx b/frontend/components/navigation/sidebar/sidebar-other-topics.tsx new file mode 100644 index 00000000..1c34e038 --- /dev/null +++ b/frontend/components/navigation/sidebar/sidebar-other-topics.tsx @@ -0,0 +1,50 @@ +import { ComponentProps } from "react"; +import { + Building2, + DollarSign, + Film, + HeartHandshake, + Leaf, + Medal, + Microscope, + Palette, + Scale, + UsersRound, +} from "lucide-react"; + +import { Categories } from "@/types/categories"; + +import SidebarItemWithIcon from "./sidebar-item-with-icon"; + +interface TopicItem extends ComponentProps {} + +// TODO: dynamically fetch +const otherTopics: TopicItem[] = [ + { Icon: Microscope, label: Categories.SciTech }, + { Icon: Palette, label: Categories.ArtsHumanities }, + { Icon: Building2, label: Categories.Politics }, + { Icon: Film, label: Categories.Media }, + { Icon: Leaf, label: Categories.Environment }, + { Icon: DollarSign, label: Categories.Economics }, + { Icon: Medal, label: Categories.Sports }, + { Icon: Scale, label: Categories.GenderEquality }, + { Icon: HeartHandshake, label: Categories.Religion }, + { Icon: UsersRound, label: Categories.SocietyCulture }, +]; + +const SidebarOtherTopics = () => { + return ( +
+

+ Other topics +

+
+ {otherTopics.map((topicItem) => ( + + ))} +
+
+ ); +}; + +export default SidebarOtherTopics; diff --git a/frontend/components/navigation/sidebar/sidebar.tsx b/frontend/components/navigation/sidebar/sidebar.tsx index 04bcbe3a..d4f66fc7 100644 --- a/frontend/components/navigation/sidebar/sidebar.tsx +++ b/frontend/components/navigation/sidebar/sidebar.tsx @@ -1,10 +1,12 @@ import UserProfileButton from "@/components/auth/user-profile-button"; +import SidebarOtherTopics from "@/components/navigation/sidebar/sidebar-other-topics"; /* Assumption: This component is only rendered if the user is logged in */ const Sidebar = () => { return ( -
+
+
); }; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 21740ac7..c08d88a0 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -50,6 +50,7 @@ const config: Config = { "4": "hsl(var(--chart-4))", "5": "hsl(var(--chart-5))", }, + offblack: "#373530", }, borderRadius: { lg: "var(--radius)", diff --git a/frontend/types/categories.ts b/frontend/types/categories.ts new file mode 100644 index 00000000..95fb0710 --- /dev/null +++ b/frontend/types/categories.ts @@ -0,0 +1,12 @@ +export enum Categories { + SciTech = "Science & technology", + ArtsHumanities = "Arts & humanities", + Politics = "Politics", + Media = "Media", + Environment = "Environment", + Economics = "Economics", + Sports = "Sports", + GenderEquality = "Gender & equality", + Religion = "Religion", + SocietyCulture = "Society & culture", +} From a1b4b15f4963299281af1238917ba6c268799be3 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 00:35:04 +0800 Subject: [PATCH 19/28] fix: sidebar responsiveness size --- frontend/components/layout/app-layout.tsx | 27 ++++++++++++------- .../components/navigation/sidebar/sidebar.tsx | 2 +- frontend/hooks/use-breakpoint-media-query.ts | 8 ++++-- frontend/utils/media.ts | 3 ++- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index 997493f7..e3088fb1 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -21,40 +21,47 @@ const breakpointConfigMap: Record< ComponentProps > = { [MediaBreakpoint.Sm]: { - defaultSize: 30, - maxSize: 35, - minSize: 10, + defaultSize: 50, + maxSize: 60, + minSize: 30, collapsible: true, collapsedSize: 1, }, [MediaBreakpoint.Md]: { - defaultSize: 25, + defaultSize: 20, maxSize: 40, - minSize: 20, + minSize: 16, collapsible: true, collapsedSize: 1, }, [MediaBreakpoint.Lg]: { - defaultSize: 25, + defaultSize: 20, maxSize: 40, minSize: 20, collapsible: true, collapsedSize: 1, }, [MediaBreakpoint.Xl]: { - defaultSize: 20, + defaultSize: 16, maxSize: 35, minSize: 15, collapsible: true, collapsedSize: 1, }, - [MediaBreakpoint.XXl]: { - defaultSize: 25, - maxSize: 35, + [MediaBreakpoint.Xxl]: { + defaultSize: 10, + maxSize: 25, minSize: 15, collapsible: true, collapsedSize: 1, }, + [MediaBreakpoint.Xxxl]: { + defaultSize: 8, + maxSize: 25, + minSize: 5, + collapsible: true, + collapsedSize: 1, + }, }; const AppLayout = ({ children }: { children: ReactNode }) => { diff --git a/frontend/components/navigation/sidebar/sidebar.tsx b/frontend/components/navigation/sidebar/sidebar.tsx index d4f66fc7..546abbf0 100644 --- a/frontend/components/navigation/sidebar/sidebar.tsx +++ b/frontend/components/navigation/sidebar/sidebar.tsx @@ -4,7 +4,7 @@ import SidebarOtherTopics from "@/components/navigation/sidebar/sidebar-other-to /* Assumption: This component is only rendered if the user is logged in */ const Sidebar = () => { return ( -
+
diff --git a/frontend/hooks/use-breakpoint-media-query.ts b/frontend/hooks/use-breakpoint-media-query.ts index be45caeb..af784809 100644 --- a/frontend/hooks/use-breakpoint-media-query.ts +++ b/frontend/hooks/use-breakpoint-media-query.ts @@ -6,9 +6,13 @@ function useBreakpointMediaQuery(): MediaBreakpoint { const isMdBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Md})`); const isLgBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Lg})`); const isXlBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Xl})`); - const isXxlBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.XXl})`); + const isXxlBreakpoint = useMediaQuery(`(min-width: ${MediaBreakpoint.Xxl})`); + const isXxxlBreakpoint = useMediaQuery( + `(min-width: ${MediaBreakpoint.Xxxl})`, + ); - if (isXxlBreakpoint) return MediaBreakpoint.XXl; + if (isXxxlBreakpoint) return MediaBreakpoint.Xxxl; + if (isXxlBreakpoint) return MediaBreakpoint.Xxl; if (isXlBreakpoint) return MediaBreakpoint.Xl; if (isMdBreakpoint) return MediaBreakpoint.Md; if (isLgBreakpoint) return MediaBreakpoint.Lg; diff --git a/frontend/utils/media.ts b/frontend/utils/media.ts index 0100013a..cdc093ee 100644 --- a/frontend/utils/media.ts +++ b/frontend/utils/media.ts @@ -3,5 +3,6 @@ export enum MediaBreakpoint { Md = "768px", Lg = "1024px", Xl = "1280px", - XXl = "1536px", + Xxl = "1920px", + Xxxl = "2560px", } From c40f7932a8622e976a158385a942ca732cd7af5e Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 01:03:25 +0800 Subject: [PATCH 20/28] fix: user store shape --- frontend/app/login/page.tsx | 3 +-- frontend/app/not-found.tsx | 5 ++++- frontend/components/auth/user-profile-button.tsx | 5 +++-- frontend/components/layout/app-layout.tsx | 2 +- frontend/components/navigation/navbar.tsx | 2 +- frontend/store/user/user-store.ts | 13 ++++++------- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index c641d63a..6c748be1 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -58,8 +58,7 @@ function LoginPage() { setIsError(true); } else { setIsError(false); - const { id: userId, email } = response.data.user; - setLoggedIn(userId, email); + setLoggedIn(response.data.user); router.push("/"); } }; diff --git a/frontend/app/not-found.tsx b/frontend/app/not-found.tsx index dd7281a5..5772572b 100644 --- a/frontend/app/not-found.tsx +++ b/frontend/app/not-found.tsx @@ -1,4 +1,5 @@ import JippyClown from "@/assets/jippy-clown"; +import Link from "@/components/navigation/link"; import { Button } from "@/components/ui/button"; function AboutPage() { @@ -20,7 +21,9 @@ function AboutPage() { The page you were looking for doesn't exist. The page might have moved, or you might have clicked on a bad link.

- + + +
); diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index 1087ee11..5062c934 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -24,9 +24,9 @@ import { useUserStore } from "@/store/user/user-store-provider"; import { getInitialFromEmail, getNameFromEmail } from "@/utils/string"; const UserProfileButton = () => { - const [isOpen, setIsOpen] = useState(false); - const { email, setNotLoggedIn } = useUserStore((state) => state); const router = useRouter(); + const [isOpen, setIsOpen] = useState(false); + const { user, setNotLoggedIn } = useUserStore((state) => state); const signout = async () => { await logoutAuthLogoutGet({ withCredentials: true }); @@ -34,6 +34,7 @@ const UserProfileButton = () => { router.push("/"); }; + const email = user.email; return (
setIsOpen(isOpen)}> diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index e3088fb1..9558d3ca 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -75,7 +75,7 @@ const AppLayout = ({ children }: { children: ReactNode }) => { useEffect(() => { if (isUserProfileSuccess && userProfile) { - setLoggedIn(userProfile.id, userProfile.email); + setLoggedIn(userProfile); } else { setNotLoggedIn(); } diff --git a/frontend/components/navigation/navbar.tsx b/frontend/components/navigation/navbar.tsx index 2085e657..d52ca89d 100644 --- a/frontend/components/navigation/navbar.tsx +++ b/frontend/components/navigation/navbar.tsx @@ -28,7 +28,7 @@ function Navbar() { useEffect(() => { if (isUserProfileSuccess && userProfile) { - setLoggedIn(userProfile.id, userProfile.email); + setLoggedIn(userProfile); } else { setNotLoggedIn(); } diff --git a/frontend/store/user/user-store.ts b/frontend/store/user/user-store.ts index 6ad7209d..7e54a067 100644 --- a/frontend/store/user/user-store.ts +++ b/frontend/store/user/user-store.ts @@ -1,9 +1,10 @@ import { createStore } from "zustand"; +import { UserPublic } from "@/client"; + interface UserState { isLoggedIn: boolean; - userId?: number; - email?: string; + user?: UserPublic; } export const defaultUserState: UserState = { @@ -11,7 +12,7 @@ export const defaultUserState: UserState = { }; interface UserActions { - setLoggedIn: (userId?: number, email?: string) => void; + setLoggedIn: (user: UserPublic) => void; setNotLoggedIn: () => void; } @@ -20,13 +21,11 @@ export type UserStore = UserState & UserActions; export const createUserStore = (initState: UserState = defaultUserState) => { return createStore()((set) => ({ ...initState, - setLoggedIn: (userId, email) => - set(() => ({ isLoggedIn: true, userId, email })), + setLoggedIn: (user) => set(() => ({ isLoggedIn: true, user })), setNotLoggedIn: () => set(() => ({ isLoggedIn: false, - userId: undefined, - email: undefined, + user: undefined, })), })); }; From 61b11d5f1a5ddc236751d2b86e94d42db0c8c209 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 01:03:42 +0800 Subject: [PATCH 21/28] feat: add user profile skeleton --- frontend/components/auth/user-profile-button.tsx | 15 +++++++++++++++ frontend/components/ui/skeleton.tsx | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 frontend/components/ui/skeleton.tsx diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index 5062c934..6f23a883 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -20,6 +20,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { Skeleton } from "@/components/ui/skeleton"; import { useUserStore } from "@/store/user/user-store-provider"; import { getInitialFromEmail, getNameFromEmail } from "@/utils/string"; @@ -34,6 +35,20 @@ const UserProfileButton = () => { router.push("/"); }; + if (!user) { + return ( +
+
+ +
+ + +
+
+
+ ); + } + const email = user.email; return (
diff --git a/frontend/components/ui/skeleton.tsx b/frontend/components/ui/skeleton.tsx new file mode 100644 index 00000000..01b8b6d4 --- /dev/null +++ b/frontend/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } From 5da437c9f6acf65b0c9e29aa87f1b12fa44f5189 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 04:29:29 +0800 Subject: [PATCH 22/28] refactor: categories --- frontend/components/layout/app-layout.tsx | 6 +- .../sidebar/sidebar-other-topics.tsx | 56 +++++++++---------- frontend/components/ui/skeleton.tsx | 6 +- frontend/types/categories.ts | 43 +++++++++++++- 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/frontend/components/layout/app-layout.tsx b/frontend/components/layout/app-layout.tsx index 9558d3ca..42785d2b 100644 --- a/frontend/components/layout/app-layout.tsx +++ b/frontend/components/layout/app-layout.tsx @@ -21,16 +21,16 @@ const breakpointConfigMap: Record< ComponentProps > = { [MediaBreakpoint.Sm]: { - defaultSize: 50, + defaultSize: 25, maxSize: 60, - minSize: 30, + minSize: 20, collapsible: true, collapsedSize: 1, }, [MediaBreakpoint.Md]: { defaultSize: 20, maxSize: 40, - minSize: 16, + minSize: 10, collapsible: true, collapsedSize: 1, }, diff --git a/frontend/components/navigation/sidebar/sidebar-other-topics.tsx b/frontend/components/navigation/sidebar/sidebar-other-topics.tsx index 1c34e038..8e086e11 100644 --- a/frontend/components/navigation/sidebar/sidebar-other-topics.tsx +++ b/frontend/components/navigation/sidebar/sidebar-other-topics.tsx @@ -1,35 +1,25 @@ import { ComponentProps } from "react"; -import { - Building2, - DollarSign, - Film, - HeartHandshake, - Leaf, - Medal, - Microscope, - Palette, - Scale, - UsersRound, -} from "lucide-react"; -import { Categories } from "@/types/categories"; +import { + categoriesToDisplayName, + categoriesToIconsMap, + Category, +} from "@/types/categories"; import SidebarItemWithIcon from "./sidebar-item-with-icon"; -interface TopicItem extends ComponentProps {} - // TODO: dynamically fetch -const otherTopics: TopicItem[] = [ - { Icon: Microscope, label: Categories.SciTech }, - { Icon: Palette, label: Categories.ArtsHumanities }, - { Icon: Building2, label: Categories.Politics }, - { Icon: Film, label: Categories.Media }, - { Icon: Leaf, label: Categories.Environment }, - { Icon: DollarSign, label: Categories.Economics }, - { Icon: Medal, label: Categories.Sports }, - { Icon: Scale, label: Categories.GenderEquality }, - { Icon: HeartHandshake, label: Categories.Religion }, - { Icon: UsersRound, label: Categories.SocietyCulture }, +const otherTopics = [ + Category.SciTech, + Category.ArtsHumanities, + Category.Politics, + Category.Media, + Category.Environment, + Category.Economics, + Category.Sports, + Category.GenderEquality, + Category.Religion, + Category.SocietyCulture, ]; const SidebarOtherTopics = () => { @@ -39,9 +29,17 @@ const SidebarOtherTopics = () => { Other topics

- {otherTopics.map((topicItem) => ( - - ))} + {otherTopics.map((topicItem) => { + const categoryLabel = categoriesToDisplayName[topicItem]; + const categoryIcon = categoriesToIconsMap[topicItem]; + return ( + + ); + })}
); diff --git a/frontend/components/ui/skeleton.tsx b/frontend/components/ui/skeleton.tsx index 01b8b6d4..2cdf440d 100644 --- a/frontend/components/ui/skeleton.tsx +++ b/frontend/components/ui/skeleton.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; function Skeleton({ className, @@ -9,7 +9,7 @@ function Skeleton({ className={cn("animate-pulse rounded-md bg-muted", className)} {...props} /> - ) + ); } -export { Skeleton } +export { Skeleton }; diff --git a/frontend/types/categories.ts b/frontend/types/categories.ts index 95fb0710..16312619 100644 --- a/frontend/types/categories.ts +++ b/frontend/types/categories.ts @@ -1,4 +1,19 @@ -export enum Categories { +import { + Building2, + Car, + DollarSign, + Film, + HeartHandshake, + Leaf, + LucideIcon, + Medal, + Microscope, + Palette, + Scale, + UsersRound, +} from "lucide-react"; + +export enum Category { SciTech = "Science & technology", ArtsHumanities = "Arts & humanities", Politics = "Politics", @@ -10,3 +25,29 @@ export enum Categories { Religion = "Religion", SocietyCulture = "Society & culture", } + +export const categoriesToDisplayName: Record = { + [Category.SciTech]: "Science & technology", + [Category.ArtsHumanities]: "Arts & humanities", + [Category.Politics]: "Politics", + [Category.Media]: "Media", + [Category.Environment]: "Environment", + [Category.Economics]: "Economics", + [Category.Sports]: "Sports", + [Category.GenderEquality]: "Gender & equality", + [Category.Religion]: "Religion", + [Category.SocietyCulture]: "Society & culture", +}; + +export const categoriesToIconsMap: Record = { + [Category.SciTech]: Microscope, + [Category.ArtsHumanities]: Palette, + [Category.Politics]: Building2, + [Category.Media]: Film, + [Category.Environment]: Leaf, + [Category.Economics]: DollarSign, + [Category.Sports]: Medal, + [Category.GenderEquality]: Scale, + [Category.Religion]: HeartHandshake, + [Category.SocietyCulture]: UsersRound, +}; From c1a72c02f925ea35e85e077b6e2551855cbcce45 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 04:30:06 +0800 Subject: [PATCH 23/28] feat: root page skeleton --- frontend/app/home.tsx | 42 +++++++++++++++ frontend/app/landing.tsx | 6 +++ frontend/app/page.tsx | 108 ++++----------------------------------- 3 files changed, 57 insertions(+), 99 deletions(-) create mode 100644 frontend/app/home.tsx create mode 100644 frontend/app/landing.tsx diff --git a/frontend/app/home.tsx b/frontend/app/home.tsx new file mode 100644 index 00000000..9191e29b --- /dev/null +++ b/frontend/app/home.tsx @@ -0,0 +1,42 @@ +import Chip from "@/components/display/chip"; +import NewsArticle from "@/components/news/news-article"; +import { useUserStore } from "@/store/user/user-store-provider"; +import { + categoriesToDisplayName, + categoriesToIconsMap, + Category, +} from "@/types/categories"; +import { + ArrowUpLeft, + ArrowUpLeftIcon, + ArrowUpRightIcon, + Cat, + ExternalLinkIcon, +} from "lucide-react"; +import Image from "next/image"; + +/* This component should only be rendered to authenticated users */ +const Home = () => { + const user = useUserStore((store) => store.user); + return ( +
+
+ + {new Date().toDateString()} + +

+ What happened this week +

+
+ +
+ + + + +
+
+ ); +}; + +export default Home; diff --git a/frontend/app/landing.tsx b/frontend/app/landing.tsx new file mode 100644 index 00000000..240621a1 --- /dev/null +++ b/frontend/app/landing.tsx @@ -0,0 +1,6 @@ +const Landing = () => { + // TODO: build landing page + return <>; +}; + +export default Landing; diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 0df827d2..e983df1e 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,102 +1,12 @@ -import Image from "next/image"; +"use client"; -export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
+import Home from "@/app/home"; +import Landing from "@/app/landing"; +import { useUserStore } from "@/store/user/user-store-provider"; - -
+const RootPage = () => { + const isLoggedIn = useUserStore((store) => store.isLoggedIn); + return isLoggedIn ? : ; +}; - -
- ); -} +export default RootPage; From 58c921b9b30d745e5049583ea54da2aa89472fff Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 04:31:16 +0800 Subject: [PATCH 24/28] feat: add news article --- frontend/components/display/chip.tsx | 9 +++- frontend/components/news/news-article.tsx | 57 +++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 frontend/components/news/news-article.tsx diff --git a/frontend/components/display/chip.tsx b/frontend/components/display/chip.tsx index bf2759f4..e8dcdfe5 100644 --- a/frontend/components/display/chip.tsx +++ b/frontend/components/display/chip.tsx @@ -2,6 +2,7 @@ import { cva, VariantProps } from "class-variance-authority"; import { Box } from "@/components/ui/box"; import { cn } from "@/lib/utils"; +import { LucideIcon } from "lucide-react"; const chipVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-xs font-medium transition-colors disabled:pointer-events-none disabled:opacity-50", @@ -10,6 +11,8 @@ const chipVariants = cva( variant: { default: "bg-sky-200/60 text-sky-600 hover:bg-sky-200/40", destructive: "bg-red-200/60 text-destructive/90 hover:bg-red-200/40", + green: "bg-[#C5EBD9]/60 text-[#2D835A] hover:bg-[#C5EBD9]/40", + greygreen: "bg-[#D2DAD6]/60 text-[#4C6559] hover:bg-[#D2DAD6]/40", }, size: { default: "h-6 px-2 py-2", @@ -26,12 +29,14 @@ const chipVariants = cva( interface ChipProps extends VariantProps { label: string; - className: string; + className?: string; + Icon?: LucideIcon; } -const Chip = ({ label, variant, size, className }: ChipProps) => { +const Chip = ({ label, variant, size, className, Icon }: ChipProps) => { return ( + {Icon && } {label} ); diff --git a/frontend/components/news/news-article.tsx b/frontend/components/news/news-article.tsx new file mode 100644 index 00000000..0d4a9dcc --- /dev/null +++ b/frontend/components/news/news-article.tsx @@ -0,0 +1,57 @@ +import { + categoriesToDisplayName, + categoriesToIconsMap, + Category, +} from "@/types/categories"; +import { ArrowUpRightIcon } from "lucide-react"; +import Chip from "../display/chip"; +import Image from "next/image"; + +const sampleArticleCategories = [ + Category.Economics, + Category.Environment, + Category.Media, + Category.Politics, +]; + +const NewsArticle = () => { + return ( +
+
+
+ + CNA, Guardian + + 21 Sep 2024 +
+

+ Norris Claims Singapore GP Pole Amid Ferrari’s Setback +

+

+ A Reflection on Commercialization, Sustainability, and Global Sports + as Cultural Forces +

+
+ {sampleArticleCategories.map((category) => ( + + ))} +
+
+
+ +
+
+ ); +}; + +export default NewsArticle; From 7050b6444ef77e7f69de1c978e7e869c97b69e59 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 04:31:43 +0800 Subject: [PATCH 25/28] feat: add mobile responsiveness for news article --- frontend/components/news/news-article.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/components/news/news-article.tsx b/frontend/components/news/news-article.tsx index 0d4a9dcc..ac372d3a 100644 --- a/frontend/components/news/news-article.tsx +++ b/frontend/components/news/news-article.tsx @@ -16,8 +16,8 @@ const sampleArticleCategories = [ const NewsArticle = () => { return ( -
-
+
+
CNA, Guardian @@ -31,7 +31,7 @@ const NewsArticle = () => { A Reflection on Commercialization, Sustainability, and Global Sports as Cultural Forces

-
+
{sampleArticleCategories.map((category) => ( { ))}
-
+
From 24bf7ecea86e0b0f2de214ac3052784d1b90b4e2 Mon Sep 17 00:00:00 2001 From: Chloe Lim Date: Tue, 24 Sep 2024 04:37:44 +0800 Subject: [PATCH 26/28] refactor: minor fixes --- frontend/app/register/page.tsx | 2 +- frontend/components/auth/user-profile-button.tsx | 4 ++-- frontend/components/form/inputs/password-input.tsx | 2 +- frontend/components/navigation/navbar.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/app/register/page.tsx b/frontend/app/register/page.tsx index 2267f77c..9b6c3b84 100644 --- a/frontend/app/register/page.tsx +++ b/frontend/app/register/page.tsx @@ -58,7 +58,7 @@ function RegisterPage() { } return ( - + {/* Header */} diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index 6f23a883..1dc30666 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -38,7 +38,7 @@ const UserProfileButton = () => { if (!user) { return (
-
+
@@ -55,7 +55,7 @@ const UserProfileButton = () => { setIsOpen(isOpen)}>
-
+
{getInitialFromEmail(email)} diff --git a/frontend/components/form/inputs/password-input.tsx b/frontend/components/form/inputs/password-input.tsx index fd50e8f8..2a156f80 100644 --- a/frontend/components/form/inputs/password-input.tsx +++ b/frontend/components/form/inputs/password-input.tsx @@ -28,7 +28,7 @@ const PasswordInput = forwardRef(

{helperText}

)}
-
+
{!isLoggedIn && (
-