diff --git a/frontend/app/App.tsx b/frontend/app/App.tsx index 9a1ef92d2..fe931a543 100644 --- a/frontend/app/App.tsx +++ b/frontend/app/App.tsx @@ -17,6 +17,7 @@ import { import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { ChatsProvider } from "@/lib/context/ChatsProvider"; import { MenuProvider } from "@/lib/context/MenuProvider/Menu-provider"; +import { NotificationsProvider } from "@/lib/context/NotificationsProvider/notifications-provider"; import { OnboardingProvider } from "@/lib/context/OnboardingProvider/Onboarding-provider"; import { SearchModalProvider } from "@/lib/context/SearchModalProvider/search-modal-provider"; import { useSupabase } from "@/lib/context/SupabaseProvider"; @@ -90,17 +91,19 @@ const AppWithQueryClient = ({ children }: PropsWithChildren): JSX.Element => { - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + diff --git a/frontend/lib/components/Menu/Menu.module.scss b/frontend/lib/components/Menu/Menu.module.scss index f100d9932..cd1d75278 100644 --- a/frontend/lib/components/Menu/Menu.module.scss +++ b/frontend/lib/components/Menu/Menu.module.scss @@ -1,9 +1,18 @@ +@use "styles/BoxShadow.module.scss"; +@use "styles/ScreenSizes.module.scss"; @use "styles/Spacings.module.scss"; +@use "styles/Variables.module.scss"; @use "styles/ZIndexes.module.scss"; .menu_container { background-color: var(--background-1); border-right: 1px solid var(--border-1); + width: Variables.$menuWidth; + transition: width 0.2s ease-in-out; + + &.hidden { + width: 0; + } .menu_wrapper { padding-top: Spacings.$spacing05; @@ -52,4 +61,26 @@ &.shifted { margin-left: 180px; } -} \ No newline at end of file +} + +.notifications_panel { + width: 400px; + position: absolute; + top: Variables.$pageHeaderHeight; + min-height: calc(100% - Variables.$pageHeaderHeight); + max-height: calc(100% - Variables.$pageHeaderHeight); + overflow: scroll; + left: calc(Variables.$menuWidth + 1px); + z-index: ZIndexes.$overlay; + border-right: 1px solid var(--border-1); + box-shadow: BoxShadow.$small; + background-color: var(--background-0); + + @media (max-width: ScreenSizes.$small) { + width: 100%; + left: 0; + min-height: 100vh; + max-height: 100vh; + top: 0; + } +} diff --git a/frontend/lib/components/Menu/Menu.tsx b/frontend/lib/components/Menu/Menu.tsx index 26ec04d55..acec6b50c 100644 --- a/frontend/lib/components/Menu/Menu.tsx +++ b/frontend/lib/components/Menu/Menu.tsx @@ -7,12 +7,15 @@ import { useChatsList } from "@/app/chat/[chatId]/hooks/useChatsList"; import { QuivrLogo } from "@/lib/assets/QuivrLogo"; import { nonProtectedPaths } from "@/lib/config/routesConfig"; import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; +import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import styles from "./Menu.module.scss"; import { AnimatedDiv } from "./components/AnimationDiv"; import { DiscussionButton } from "./components/DiscussionButton/DiscussionButton"; import { HomeButton } from "./components/HomeButton/HomeButton"; +import { Notifications } from "./components/Notifications/Notifications"; +import { NotificationsButton } from "./components/NotificationsButton/NotificationsButton"; import { ProfileButton } from "./components/ProfileButton/ProfileButton"; import { SocialsButtons } from "./components/SocialsButtons/SocialsButtons"; import { StudioButton } from "./components/StudioButton/StudioButton"; @@ -21,6 +24,7 @@ import { UpgradeToPlusButton } from "./components/UpgradeToPlusButton/UpgradeToP export const Menu = (): JSX.Element => { const { isOpened } = useMenuContext(); + const { isVisible } = useNotificationsContext(); const router = useRouter(); const pathname = usePathname() ?? ""; const [isLogoHovered, setIsLogoHovered] = useState(false); @@ -50,50 +54,62 @@ export const Menu = (): JSX.Element => { } return ( - -
- -
-
router.push("/search")} - onMouseEnter={() => setIsLogoHovered(true)} - onMouseLeave={() => setIsLogoHovered(false)} - > - -
+
+ +
+ +
+
router.push("/search")} + onMouseEnter={() => setIsLogoHovered(true)} + onMouseLeave={() => setIsLogoHovered(false)} + > + +
-
-
- - - - +
+
+ + + + + +
+
+ + +
-
- - +
+
-
- -
-
- -
-
+
+
- -
- + > + +
+ + {isVisible && ( +
+ +
+ )} +
); }; diff --git a/frontend/lib/components/PageHeader/Notifications/Notification/Notification.module.scss b/frontend/lib/components/Menu/components/Notifications/Notification/Notification.module.scss similarity index 83% rename from frontend/lib/components/PageHeader/Notifications/Notification/Notification.module.scss rename to frontend/lib/components/Menu/components/Notifications/Notification/Notification.module.scss index 60819d828..3edd2d0a4 100644 --- a/frontend/lib/components/PageHeader/Notifications/Notification/Notification.module.scss +++ b/frontend/lib/components/Menu/components/Notifications/Notification/Notification.module.scss @@ -3,7 +3,8 @@ @use "styles/Typography.module.scss"; .notification_wrapper { - padding: Spacings.$spacing03; + padding-block: Spacings.$spacing04; + padding-inline: Spacings.$spacing06; display: flex; flex-direction: column; gap: Spacings.$spacing02; @@ -18,25 +19,27 @@ display: flex; justify-content: space-between; align-items: center; - font-size: Typography.$small; overflow: hidden; gap: Spacings.$spacing06; .left { display: flex; align-items: center; - gap: Spacings.$spacing02; + gap: Spacings.$spacing03; overflow: hidden; .badge { min-width: 6px; + max-width: 6px; min-height: 6px; + max-height: 6px; border-radius: Radius.$circle; background-color: var(--primary-0); } .title { @include Typography.EllipsisOverflow; + font-size: Typography.$tiny; } } @@ -69,7 +72,7 @@ } .date { - font-size: Typography.$tiny; + font-size: Typography.$very-tiny; color: var(--text-2); } } diff --git a/frontend/lib/components/PageHeader/Notifications/Notification/Notification.tsx b/frontend/lib/components/Menu/components/Notifications/Notification/Notification.tsx similarity index 95% rename from frontend/lib/components/PageHeader/Notifications/Notification/Notification.tsx rename to frontend/lib/components/Menu/components/Notifications/Notification/Notification.tsx index 21a7cc554..e832a1975 100644 --- a/frontend/lib/components/PageHeader/Notifications/Notification/Notification.tsx +++ b/frontend/lib/components/Menu/components/Notifications/Notification/Notification.tsx @@ -5,7 +5,7 @@ import { useSupabase } from "@/lib/context/SupabaseProvider"; import styles from "./Notification.module.scss"; -import { NotificationType } from "../types/types"; +import { NotificationType } from "../../../types/types"; interface NotificationProps { notification: NotificationType; @@ -50,14 +50,14 @@ export const Notification = ({ name={notification.read ? "unread" : "read"} color="black" handleHover={true} - size="normal" + size="small" onClick={() => readNotif()} /> deleteNotif()} />
diff --git a/frontend/lib/components/Menu/components/Notifications/Notifications.module.scss b/frontend/lib/components/Menu/components/Notifications/Notifications.module.scss new file mode 100644 index 000000000..d298c1e9b --- /dev/null +++ b/frontend/lib/components/Menu/components/Notifications/Notifications.module.scss @@ -0,0 +1,43 @@ +@use "styles/BoxShadow.module.scss"; +@use "styles/Radius.module.scss"; +@use "styles/Spacings.module.scss"; +@use "styles/Typography.module.scss"; +@use "styles/ZIndexes.module.scss"; + +.notifications_wrapper { + position: relative; + z-index: ZIndexes.$overlay; + + .notifications_panel_header { + display: flex; + align-items: center; + justify-content: space-between; + font-size: Typography.$small; + padding-inline: Spacings.$spacing05; + padding-top: Spacings.$spacing03; + padding-bottom: Spacings.$spacing05; + + .left { + display: flex; + align-items: center; + gap: Spacings.$spacing03; + + .title { + color: var(--text-2); + font-size: Typography.$small; + font-weight: 500; + } + } + + .buttons { + display: flex; + gap: Spacings.$spacing02; + } + } + + .no_notifications { + padding: Spacings.$spacing05; + font-size: Typography.$tiny; + color: var(--text-2); + } +} diff --git a/frontend/lib/components/Menu/components/Notifications/Notifications.tsx b/frontend/lib/components/Menu/components/Notifications/Notifications.tsx new file mode 100644 index 000000000..c045b63a4 --- /dev/null +++ b/frontend/lib/components/Menu/components/Notifications/Notifications.tsx @@ -0,0 +1,109 @@ +import { useEffect } from "react"; + +import Icon from "@/lib/components/ui/Icon/Icon"; +import TextButton from "@/lib/components/ui/TextButton/TextButton"; +import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; +import { useSupabase } from "@/lib/context/SupabaseProvider"; +import { useDevice } from "@/lib/hooks/useDevice"; + +import { Notification } from "./Notification/Notification"; +import styles from "./Notifications.module.scss"; + +export const Notifications = (): JSX.Element => { + const { + notifications, + updateNotifications, + unreadNotifications, + setIsVisible, + } = useNotificationsContext(); + const { supabase } = useSupabase(); + const { isMobile } = useDevice(); + + const deleteAllNotifications = async () => { + for (const notification of notifications) { + await supabase.from("notifications").delete().eq("id", notification.id); + } + await updateNotifications(); + }; + + const markAllAsRead = async () => { + for (const notification of notifications) { + await supabase + .from("notifications") + .update({ read: true }) + .eq("id", notification.id); + } + await updateNotifications(); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as Node; + const panel = document.getElementById("notifications-panel"); + const button = document.getElementById("notifications-button"); + + if (!panel?.contains(target) && !button?.contains(target)) { + setIsVisible(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+
+
+
+ {isMobile && ( + setIsVisible(false)} + /> + )} + Notifications +
+
+ void markAllAsRead()} + disabled={unreadNotifications === 0} + small={true} + /> + | + void deleteAllNotifications()} + disabled={notifications.length === 0} + small={true} + /> +
+
+ {notifications.length === 0 && ( +
+ You have no notifications +
+ )} + {notifications.map((notification, i) => ( + + ))} +
+
+ ); +}; + +export default Notifications; diff --git a/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.module.scss b/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.module.scss new file mode 100644 index 000000000..62aa8716b --- /dev/null +++ b/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.module.scss @@ -0,0 +1,31 @@ +@use "styles/Radius.module.scss"; +@use "styles/Spacings.module.scss"; +@use "styles/Typography.module.scss"; + +.button_wrapper { + display: flex; + gap: Spacings.$spacing02; + align-items: center; + border-radius: Radius.$normal; + justify-content: space-between; + padding-right: Spacings.$spacing03; + cursor: pointer; + + .badge { + color: var(--white-0); + background-color: var(--dangerous-dark); + border-radius: Radius.$normal; + width: 18px; + height: 18px; + display: flex; + justify-content: center; + align-items: center; + font-size: Typography.$very_tiny; + top: -(Spacings.$spacing03); + right: -(Spacings.$spacing02); + } + + &:hover { + background-color: var(--background-3); + } +} diff --git a/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.tsx b/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.tsx new file mode 100644 index 000000000..72a946b95 --- /dev/null +++ b/frontend/lib/components/Menu/components/NotificationsButton/NotificationsButton.tsx @@ -0,0 +1,58 @@ +import { useEffect } from "react"; + +import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton"; +import { useNotificationsContext } from "@/lib/context/NotificationsProvider/hooks/useNotificationsContext"; +import { useSupabase } from "@/lib/context/SupabaseProvider"; + +import styles from "./NotificationsButton.module.scss"; + +export const NotificationsButton = (): JSX.Element => { + const { isVisible, setIsVisible, unreadNotifications, updateNotifications } = + useNotificationsContext(); + const { supabase } = useSupabase(); + + useEffect(() => { + const channel = supabase + .channel("notifications") + .on( + "postgres_changes", + { event: "*", schema: "public", table: "notifications" }, + () => { + void updateNotifications(); + } + ) + .subscribe(); + + return () => { + void supabase.removeChannel(channel); + }; + }, []); + + useEffect(() => { + void updateNotifications(); + }, []); + + return ( +
{ + setIsVisible(!isVisible); + event.preventDefault(); + event.nativeEvent.stopImmediatePropagation(); + }} + id="notifications-button" + > + + {!!unreadNotifications && ( + + {unreadNotifications > 9 ? "9+" : unreadNotifications} + + )} +
+ ); +}; diff --git a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss b/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss index ac6bf8d13..12c709e40 100644 --- a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss +++ b/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.module.scss @@ -3,5 +3,12 @@ .socials_buttons_wrapper { display: flex; gap: Spacings.$spacing05; - justify-content: center; -} \ No newline at end of file + justify-content: space-between; + padding-inline: Spacings.$spacing06; + + .left { + display: flex; + gap: Spacings.$spacing05; + justify-content: center; + } +} diff --git a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx b/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx index 904868b6c..0b486a04a 100644 --- a/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx +++ b/frontend/lib/components/Menu/components/SocialsButtons/SocialsButtons.tsx @@ -1,41 +1,66 @@ +import { useEffect, useState } from "react"; + import { Icon } from "@/lib/components/ui/Icon/Icon"; +import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import styles from "./SocialsButtons.module.scss"; export const SocialsButtons = (): JSX.Element => { + const { isDarkMode, setIsDarkMode } = useUserSettingsContext(); + const [lightModeIconName, setLightModeIconName] = useState("sun"); + + const toggleTheme = () => { + setIsDarkMode(!isDarkMode); + }; + + useEffect(() => { + setLightModeIconName(isDarkMode ? "sun" : "moon"); + }, [isDarkMode]); + const handleClick = (url: string) => { window.open(url, "_blank"); }; return (
+
+ handleClick("https://github.com/QuivrHQ/quivr")} + /> + + handleClick("https://www.linkedin.com/company/getquivr") + } + /> + handleClick("https://twitter.com/quivr_brain")} + /> + handleClick("https://discord.gg/HUpRgp2HG8")} + /> +
handleClick("https://github.com/QuivrHQ/quivr")} - /> - handleClick("https://www.linkedin.com/company/getquivr")} - /> - handleClick("https://twitter.com/quivr_brain")} - /> - handleClick("https://discord.gg/HUpRgp2HG8")} + size="normal" + onClick={toggleTheme} />
); diff --git a/frontend/lib/components/PageHeader/Notifications/types/types.ts b/frontend/lib/components/Menu/types/types.ts similarity index 100% rename from frontend/lib/components/PageHeader/Notifications/types/types.ts rename to frontend/lib/components/Menu/types/types.ts diff --git a/frontend/lib/components/PageHeader/Notifications/Notifications.module.scss b/frontend/lib/components/PageHeader/Notifications/Notifications.module.scss deleted file mode 100644 index 04a5821cb..000000000 --- a/frontend/lib/components/PageHeader/Notifications/Notifications.module.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use "styles/BoxShadow.module.scss"; -@use "styles/Radius.module.scss"; -@use "styles/Spacings.module.scss"; -@use "styles/Typography.module.scss"; -@use "styles/ZIndexes.module.scss"; - -.notifications_wrapper { - position: relative; - z-index: ZIndexes.$overlay; - - .badge { - position: absolute; - color: var(--white-0); - background-color: var(--dangerous-dark); - border-radius: Radius.$normal; - width: 16px; - height: 16px; - display: flex; - justify-content: center; - align-items: center; - font-size: Typography.$very_tiny; - top: -(Spacings.$spacing03); - right: -(Spacings.$spacing02); - } - - .notifications_panel { - position: absolute; - background-color: var(--background-0); - width: 400px; - max-height: 60vh; - overflow: scroll; - right: 0; - top: Spacings.$spacing07; - border-radius: Radius.$normal; - border: 1px solid var(--border-2); - box-shadow: BoxShadow.$large; - - .notifications_panel_header { - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid var(--border-2); - font-size: Typography.$small; - padding: Spacings.$spacing03; - - .title { - color: var(--text-2); - } - - .buttons { - display: flex; - gap: Spacings.$spacing02; - } - } - - .no_notifications { - padding: Spacings.$spacing05; - font-size: Typography.$tiny; - color: var(--text-2); - } - } -} diff --git a/frontend/lib/components/PageHeader/Notifications/Notifications.tsx b/frontend/lib/components/PageHeader/Notifications/Notifications.tsx deleted file mode 100644 index e7ef74fc0..000000000 --- a/frontend/lib/components/PageHeader/Notifications/Notifications.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useEffect, useState } from "react"; - -import { useSupabase } from "@/lib/context/SupabaseProvider"; - -import { Notification } from "./Notification/Notification"; -import styles from "./Notifications.module.scss"; -import { NotificationType } from "./types/types"; - -import { Icon } from "../../ui/Icon/Icon"; -import { TextButton } from "../../ui/TextButton/TextButton"; - -export const Notifications = (): JSX.Element => { - const [notifications, setNotifications] = useState([]); - const [unreadNotifications, setUnreadNotifications] = useState(0); - const [panelOpened, setPanelOpened] = useState(false); - const { supabase } = useSupabase(); - - const updateNotifications = async () => { - try { - let notifs = (await supabase.from("notifications").select()).data; - if (notifs) { - notifs = notifs.sort( - (a: NotificationType, b: NotificationType) => - new Date(b.datetime).getTime() - new Date(a.datetime).getTime() - ); - } - setNotifications(notifs ?? []); - setUnreadNotifications( - notifs?.filter((n: NotificationType) => !n.read).length ?? 0 - ); - } catch (error) { - console.error(error); - } - }; - - const deleteAllNotifications = async () => { - for (const notification of notifications) { - await supabase.from("notifications").delete().eq("id", notification.id); - } - await updateNotifications(); - }; - - const markAllAsRead = async () => { - for (const notification of notifications) { - await supabase - .from("notifications") - .update({ read: true }) - .eq("id", notification.id); - } - await updateNotifications(); - }; - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as Node; - const panel = document.getElementById("notifications-panel"); - - if (!panel?.contains(target)) { - setPanelOpened(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - useEffect(() => { - const channel = supabase - .channel("notifications") - .on( - "postgres_changes", - { event: "*", schema: "public", table: "notifications" }, - () => { - void updateNotifications(); - } - ) - .subscribe(); - - return () => { - void supabase.removeChannel(channel); - }; - }, []); - - useEffect(() => { - void (async () => { - await updateNotifications(); - })(); - }, []); - - return ( -
-
{ - setPanelOpened(!panelOpened); - event.nativeEvent.stopImmediatePropagation(); - }} - > - - {!!unreadNotifications && ( - - {unreadNotifications > 9 ? "9+" : unreadNotifications} - - )} -
- {panelOpened && ( -
-
- Notifications -
- void markAllAsRead()} - disabled={unreadNotifications === 0} - /> - | - void deleteAllNotifications()} - disabled={notifications.length === 0} - /> -
-
- {notifications.length === 0 && ( -
- You have no notifications -
- )} - {notifications.map((notification, i) => ( - - ))} -
- )} -
- ); -}; - -export default Notifications; diff --git a/frontend/lib/components/PageHeader/PageHeader.module.scss b/frontend/lib/components/PageHeader/PageHeader.module.scss index 6567204dc..6c99dce4c 100644 --- a/frontend/lib/components/PageHeader/PageHeader.module.scss +++ b/frontend/lib/components/PageHeader/PageHeader.module.scss @@ -1,6 +1,7 @@ @use "styles/ScreenSizes.module.scss"; @use "styles/Spacings.module.scss"; @use "styles/Typography.module.scss"; +@use "styles/Variables.module.scss"; .page_header_wrapper { display: flex; @@ -9,7 +10,7 @@ padding: Spacings.$spacing04; padding-left: Spacings.$spacing09; border-bottom: 1px solid var(--border-1); - height: 3rem; + height: Variables.$pageHeaderHeight; width: 100%; .left { diff --git a/frontend/lib/components/PageHeader/PageHeader.tsx b/frontend/lib/components/PageHeader/PageHeader.tsx index 91222a6af..fe78447a7 100644 --- a/frontend/lib/components/PageHeader/PageHeader.tsx +++ b/frontend/lib/components/PageHeader/PageHeader.tsx @@ -1,12 +1,6 @@ -import Link from "next/link"; -import { useEffect, useState } from "react"; - import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; -import { useDevice } from "@/lib/hooks/useDevice"; import { ButtonType } from "@/lib/types/QuivrButton"; -import { Notifications } from "./Notifications/Notifications"; import styles from "./PageHeader.module.scss"; import { Icon } from "../ui/Icon/Icon"; @@ -24,17 +18,6 @@ export const PageHeader = ({ buttons, }: Props): JSX.Element => { const { isOpened } = useMenuContext(); - const { isDarkMode, setIsDarkMode } = useUserSettingsContext(); - const [lightModeIconName, setLightModeIconName] = useState("sun"); - const { isMobile } = useDevice(); - - const toggleTheme = () => { - setIsDarkMode(!isDarkMode); - }; - - useEffect(() => { - setLightModeIconName(isDarkMode ? "sun" : "moon"); - }, [isDarkMode]); return (
@@ -53,22 +36,6 @@ export const PageHeader = ({ hidden={button.hidden} /> ))} - {!isMobile && } - - - -
); diff --git a/frontend/lib/context/NotificationsProvider/hooks/useNotificationsContext.tsx b/frontend/lib/context/NotificationsProvider/hooks/useNotificationsContext.tsx new file mode 100644 index 000000000..3ae92cd2f --- /dev/null +++ b/frontend/lib/context/NotificationsProvider/hooks/useNotificationsContext.tsx @@ -0,0 +1,15 @@ +import { useContext } from "react"; + +import { NotificationsContext } from "../notifications-provider"; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const useNotificationsContext = () => { + const context = useContext(NotificationsContext); + if (context === undefined) { + throw new Error( + "useNotificationsContext must be used within a MenuProvider" + ); + } + + return context; +}; diff --git a/frontend/lib/context/NotificationsProvider/notifications-provider.tsx b/frontend/lib/context/NotificationsProvider/notifications-provider.tsx new file mode 100644 index 000000000..7c161bedb --- /dev/null +++ b/frontend/lib/context/NotificationsProvider/notifications-provider.tsx @@ -0,0 +1,64 @@ +import { createContext, useState } from "react"; + +import { NotificationType } from "@/lib/components/Menu/types/types"; + +import { useSupabase } from "../SupabaseProvider"; + +type NotificationsContextType = { + isVisible: boolean; + setIsVisible: React.Dispatch>; + notifications: NotificationType[]; + setNotifications: React.Dispatch>; + unreadNotifications: number; + setUnreadNotifications: React.Dispatch>; + updateNotifications: () => Promise; +}; + +export const NotificationsContext = createContext< + NotificationsContextType | undefined +>(undefined); + +export const NotificationsProvider = ({ + children, +}: { + children: React.ReactNode; +}): JSX.Element => { + const [isVisible, setIsVisible] = useState(false); + const [notifications, setNotifications] = useState([]); + const [unreadNotifications, setUnreadNotifications] = useState(0); + const { supabase } = useSupabase(); + + const updateNotifications = async () => { + try { + let notifs = (await supabase.from("notifications").select()).data; + if (notifs) { + notifs = notifs.sort( + (a: NotificationType, b: NotificationType) => + new Date(b.datetime).getTime() - new Date(a.datetime).getTime() + ); + } + setNotifications(notifs ?? []); + setUnreadNotifications( + notifs?.filter((n: NotificationType) => !n.read).length ?? 0 + ); + } catch (error) { + console.error(error); + } + }; + + return ( + + {children} + + ); +}; diff --git a/frontend/lib/helpers/iconList.ts b/frontend/lib/helpers/iconList.ts index bdfdb3ca3..dfd870515 100644 --- a/frontend/lib/helpers/iconList.ts +++ b/frontend/lib/helpers/iconList.ts @@ -47,7 +47,6 @@ import { HiBuildingOffice } from "react-icons/hi2"; import { IoIosAdd, IoIosHelpCircleOutline, - IoIosNotifications, IoIosRadio, IoMdClose, IoMdLogOut, @@ -66,6 +65,7 @@ import { import { LiaFileVideo, LiaRobotSolid } from "react-icons/lia"; import { IconType } from "react-icons/lib"; import { + LuArrowLeftFromLine, LuBrain, LuBrainCircuit, LuChevronDown, @@ -95,7 +95,11 @@ import { MdUploadFile, } from "react-icons/md"; import { PiOfficeChairFill } from "react-icons/pi"; -import { RiDeleteBackLine, RiHashtag } from "react-icons/ri"; +import { + RiDeleteBackLine, + RiHashtag, + RiNotification2Line, +} from "react-icons/ri"; import { SlOptions } from "react-icons/sl"; import { TbNetwork, TbRobot } from "react-icons/tb"; import { VscGraph } from "react-icons/vsc"; @@ -141,6 +145,7 @@ export const iconList: { [name: string]: IconType } = { graph: VscGraph, hashtag: RiHashtag, help: IoIosHelpCircleOutline, + hide: LuArrowLeftFromLine, history: MdHistory, home: IoHomeOutline, html: BsFiletypeHtml, @@ -159,7 +164,7 @@ export const iconList: { [name: string]: IconType } = { mp4: BsFiletypeMp4, mpga: FaRegFileAudio, mpeg: LiaFileVideo, - notifications: IoIosNotifications, + notifications: RiNotification2Line, office: HiBuildingOffice, odt: BsFiletypeDocx, options: SlOptions, diff --git a/frontend/styles/_Variables.module.scss b/frontend/styles/_Variables.module.scss index a638dc3ec..d3f18b254 100644 --- a/frontend/styles/_Variables.module.scss +++ b/frontend/styles/_Variables.module.scss @@ -1 +1,3 @@ $searchBarHeight: 62px; +$pageHeaderHeight: 48px; +$menuWidth: 230px;