-
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