mirror of
https://github.com/StanGirard/quivr.git
synced 2024-09-11 21:17:36 +03:00
feat(frontend): add notifications for document uploads (#2549)
# Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. ## Checklist before requesting a review Please delete options that are not relevant. - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented hard-to-understand areas - [ ] I have ideally added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged ## Screenshots (if appropriate):
This commit is contained in:
parent
8cba448e80
commit
da3880a685
@ -0,0 +1,8 @@
|
|||||||
|
alter table "public"."notifications" alter column "datetime" set default (now() AT TIME ZONE 'utc'::text);
|
||||||
|
|
||||||
|
alter table "public"."notifications" alter column "datetime" set data type timestamp with time zone using "datetime"::timestamp with time zone;
|
||||||
|
|
||||||
|
alter
|
||||||
|
publication supabase_realtime add table notifications
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
|
@use "@/styles/Typography.module.scss";
|
||||||
|
|
||||||
|
.button_wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.credits {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-size: Typography.$tiny;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,20 @@ import Link from "next/link";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import { useUserApi } from "@/lib/api/user/useUserApi";
|
||||||
import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton";
|
import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton";
|
||||||
|
import Icon from "@/lib/components/ui/Icon/Icon";
|
||||||
|
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
|
||||||
import { useUserData } from "@/lib/hooks/useUserData";
|
import { useUserData } from "@/lib/hooks/useUserData";
|
||||||
|
|
||||||
|
import styles from "./ProfileButton.module.scss";
|
||||||
|
|
||||||
export const ProfileButton = (): JSX.Element => {
|
export const ProfileButton = (): JSX.Element => {
|
||||||
const pathname = usePathname() ?? "";
|
const pathname = usePathname() ?? "";
|
||||||
const isSelected = pathname.includes("/user");
|
const isSelected = pathname.includes("/user");
|
||||||
const { userIdentityData } = useUserData();
|
const { userIdentityData } = useUserData();
|
||||||
|
const { getUserCredits } = useUserApi();
|
||||||
|
const { remainingCredits, setRemainingCredits } = useUserSettingsContext();
|
||||||
|
|
||||||
let username = userIdentityData?.username ?? "Profile";
|
let username = userIdentityData?.username ?? "Profile";
|
||||||
|
|
||||||
@ -16,8 +23,15 @@ export const ProfileButton = (): JSX.Element => {
|
|||||||
username = userIdentityData?.username ?? "Profile";
|
username = userIdentityData?.username ?? "Profile";
|
||||||
}, [userIdentityData]);
|
}, [userIdentityData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void (async () => {
|
||||||
|
const res = await getUserCredits();
|
||||||
|
setRemainingCredits(res);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href="/user">
|
<Link className={styles.button_wrapper} href="/user">
|
||||||
<MenuButton
|
<MenuButton
|
||||||
label={username}
|
label={username}
|
||||||
iconName="user"
|
iconName="user"
|
||||||
@ -25,6 +39,12 @@ export const ProfileButton = (): JSX.Element => {
|
|||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
|
{remainingCredits !== null && (
|
||||||
|
<div className={styles.credits}>
|
||||||
|
<span className={styles.number}>{remainingCredits}</span>
|
||||||
|
<Icon name="coin" color="gold" size="normal"></Icon>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
@use "@/styles/Radius.module.scss";
|
||||||
|
@use "@/styles/Spacings.module.scss";
|
||||||
|
@use "@/styles/Typography.module.scss";
|
||||||
|
|
||||||
|
.notification_wrapper {
|
||||||
|
padding: Spacings.$spacing03;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: Spacings.$spacing02;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid var(--border-1);
|
||||||
|
|
||||||
|
&.no_border {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.read {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
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;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
min-width: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
border-radius: Radius.$circle;
|
||||||
|
background-color: var(--primary-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include Typography.EllipsisOverflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
gap: Spacings.$spacing01;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: Typography.$tiny;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
&.info {
|
||||||
|
color: var(--text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: var(--dangerous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
font-size: Typography.$tiny;
|
||||||
|
color: var(--text-2);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
import { formatDistanceToNow } from "date-fns";
|
||||||
|
|
||||||
|
import Icon from "@/lib/components/ui/Icon/Icon";
|
||||||
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
|
|
||||||
|
import styles from "./Notification.module.scss";
|
||||||
|
|
||||||
|
import { NotificationType } from "../types/types";
|
||||||
|
|
||||||
|
interface NotificationProps {
|
||||||
|
notification: NotificationType;
|
||||||
|
lastNotification?: boolean;
|
||||||
|
updateNotifications: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Notification = ({
|
||||||
|
notification,
|
||||||
|
lastNotification,
|
||||||
|
updateNotifications,
|
||||||
|
}: NotificationProps): JSX.Element => {
|
||||||
|
const { supabase } = useSupabase();
|
||||||
|
|
||||||
|
const deleteNotif = async () => {
|
||||||
|
await supabase.from("notifications").delete().eq("id", notification.id);
|
||||||
|
await updateNotifications();
|
||||||
|
};
|
||||||
|
|
||||||
|
const readNotif = async () => {
|
||||||
|
await supabase
|
||||||
|
.from("notifications")
|
||||||
|
.update({ read: !notification.read })
|
||||||
|
.eq("id", notification.id);
|
||||||
|
|
||||||
|
await updateNotifications();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles.notification_wrapper} ${
|
||||||
|
lastNotification ? styles.no_border : ""
|
||||||
|
} ${notification.read ? styles.read : ""}`}
|
||||||
|
>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.left}>
|
||||||
|
{!notification.read && <div className={styles.badge}></div>}
|
||||||
|
<span className={styles.title}>{notification.title}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.icons}>
|
||||||
|
<Icon
|
||||||
|
name={notification.read ? "unread" : "read"}
|
||||||
|
color="black"
|
||||||
|
handleHover={true}
|
||||||
|
size="normal"
|
||||||
|
onClick={() => readNotif()}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name="delete"
|
||||||
|
color="black"
|
||||||
|
handleHover={true}
|
||||||
|
size="normal"
|
||||||
|
onClick={() => deleteNotif()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={`${styles.description} ${styles[notification.status]} `}>
|
||||||
|
{notification.description}
|
||||||
|
</span>
|
||||||
|
<span className={styles.date}>
|
||||||
|
{formatDistanceToNow(new Date(notification.datetime), {
|
||||||
|
addSuffix: true,
|
||||||
|
}).replace("about ", "")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification;
|
@ -0,0 +1,62 @@
|
|||||||
|
@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(--accent);
|
||||||
|
border-radius: Radius.$circle;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: Typography.$very_tiny;
|
||||||
|
top: -(Spacings.$spacing02);
|
||||||
|
right: -(Spacings.$spacing01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
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<NotificationType[]>([]);
|
||||||
|
const [unreadNotifications, setUnreadNotifications] = useState<number>(0);
|
||||||
|
const [panelOpened, setPanelOpened] = useState<boolean>(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: !notification.read })
|
||||||
|
.eq("id", notification.id);
|
||||||
|
}
|
||||||
|
await updateNotifications();
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className={styles.notifications_wrapper}>
|
||||||
|
<div onClick={() => setPanelOpened(!panelOpened)}>
|
||||||
|
<Icon
|
||||||
|
name="notifications"
|
||||||
|
size="large"
|
||||||
|
color="black"
|
||||||
|
handleHover={true}
|
||||||
|
/>
|
||||||
|
<span className={styles.badge}>{unreadNotifications}</span>
|
||||||
|
</div>
|
||||||
|
{panelOpened && (
|
||||||
|
<div className={styles.notifications_panel}>
|
||||||
|
<div className={styles.notifications_panel_header}>
|
||||||
|
<span className={styles.title}>Notifications</span>
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<TextButton
|
||||||
|
label="Mark all as read"
|
||||||
|
color="black"
|
||||||
|
onClick={() => void markAllAsRead()}
|
||||||
|
disabled={unreadNotifications === 0}
|
||||||
|
/>
|
||||||
|
<span>|</span>
|
||||||
|
<TextButton
|
||||||
|
label="Delete all"
|
||||||
|
color="black"
|
||||||
|
onClick={() => void deleteAllNotifications()}
|
||||||
|
disabled={notifications.length === 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{notifications.length === 0 && (
|
||||||
|
<div className={styles.no_notifications}>
|
||||||
|
You have no notifications
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{notifications.map((notification, i) => (
|
||||||
|
<Notification
|
||||||
|
key={i}
|
||||||
|
notification={notification}
|
||||||
|
lastNotification={i === notifications.length - 1}
|
||||||
|
updateNotifications={updateNotifications}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notifications;
|
@ -0,0 +1,16 @@
|
|||||||
|
export enum NotificationStatus {
|
||||||
|
Info = "info",
|
||||||
|
Warning = "warning",
|
||||||
|
Error = "error",
|
||||||
|
Success = "success",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationType {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
datetime: string;
|
||||||
|
status: NotificationStatus;
|
||||||
|
archived: boolean;
|
||||||
|
read: boolean;
|
||||||
|
description: string;
|
||||||
|
}
|
@ -32,16 +32,5 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: Spacings.$spacing04;
|
gap: Spacings.$spacing04;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.credits {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: Spacings.$spacing02;
|
|
||||||
|
|
||||||
.number {
|
|
||||||
font-size: Typography.$tiny;
|
|
||||||
color: var(--gold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useUserApi } from "@/lib/api/user/useUserApi";
|
|
||||||
import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext";
|
import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext";
|
||||||
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
|
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
|
||||||
import { ButtonType } from "@/lib/types/QuivrButton";
|
import { ButtonType } from "@/lib/types/QuivrButton";
|
||||||
|
|
||||||
|
import { Notifications } from "./Notifications/Notifications";
|
||||||
import styles from "./PageHeader.module.scss";
|
import styles from "./PageHeader.module.scss";
|
||||||
|
|
||||||
import { Icon } from "../ui/Icon/Icon";
|
import { Icon } from "../ui/Icon/Icon";
|
||||||
@ -24,8 +24,6 @@ export const PageHeader = ({
|
|||||||
const { isOpened } = useMenuContext();
|
const { isOpened } = useMenuContext();
|
||||||
const { isDarkMode, setIsDarkMode } = useUserSettingsContext();
|
const { isDarkMode, setIsDarkMode } = useUserSettingsContext();
|
||||||
const [lightModeIconName, setLightModeIconName] = useState("sun");
|
const [lightModeIconName, setLightModeIconName] = useState("sun");
|
||||||
const { remainingCredits, setRemainingCredits } = useUserSettingsContext();
|
|
||||||
const { getUserCredits } = useUserApi();
|
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
setIsDarkMode(!isDarkMode);
|
setIsDarkMode(!isDarkMode);
|
||||||
@ -35,13 +33,6 @@ export const PageHeader = ({
|
|||||||
setLightModeIconName(isDarkMode ? "sun" : "moon");
|
setLightModeIconName(isDarkMode ? "sun" : "moon");
|
||||||
}, [isDarkMode]);
|
}, [isDarkMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void (async () => {
|
|
||||||
const res = await getUserCredits();
|
|
||||||
setRemainingCredits(res);
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page_header_wrapper}>
|
<div className={styles.page_header_wrapper}>
|
||||||
<div className={`${styles.left} ${!isOpened ? styles.menu_closed : ""}`}>
|
<div className={`${styles.left} ${!isOpened ? styles.menu_closed : ""}`}>
|
||||||
@ -59,12 +50,7 @@ export const PageHeader = ({
|
|||||||
hidden={button.hidden}
|
hidden={button.hidden}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{remainingCredits !== null && (
|
<Notifications />
|
||||||
<div className={styles.credits}>
|
|
||||||
<span className={styles.number}>{remainingCredits}</span>
|
|
||||||
<Icon name="coin" color="gold" size="normal"></Icon>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Icon
|
<Icon
|
||||||
name={lightModeIconName}
|
name={lightModeIconName}
|
||||||
color="black"
|
color="black"
|
||||||
|
@ -6,20 +6,25 @@
|
|||||||
gap: Spacings.$spacing03;
|
gap: Spacings.$spacing03;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
|
||||||
|
|
||||||
.black {
|
&.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.black {
|
||||||
color: var(--text-3);
|
color: var(--text-3);
|
||||||
|
|
||||||
&.hovered {
|
&:hover {
|
||||||
color: var(--primary-0);
|
color: var(--primary-0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dangerous {
|
.dangerous {
|
||||||
color: var(--dangerous);
|
color: var(--dangerous);
|
||||||
|
|
||||||
&.hovered {
|
&:hover {
|
||||||
color: var(--dangerous)-dark;
|
color: var(--dangerous-0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
import { iconList } from "@/lib/helpers/iconList";
|
import { iconList } from "@/lib/helpers/iconList";
|
||||||
import { Color } from "@/lib/types/Colors";
|
import { Color } from "@/lib/types/Colors";
|
||||||
|
|
||||||
@ -8,36 +6,25 @@ import styles from "./TextButton.module.scss";
|
|||||||
import { Icon } from "../Icon/Icon";
|
import { Icon } from "../Icon/Icon";
|
||||||
|
|
||||||
interface TextButtonProps {
|
interface TextButtonProps {
|
||||||
iconName: keyof typeof iconList;
|
iconName?: keyof typeof iconList;
|
||||||
label: string;
|
label: string;
|
||||||
color: Color;
|
color: Color;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextButton = (props: TextButtonProps): JSX.Element => {
|
export const TextButton = (props: TextButtonProps): JSX.Element => {
|
||||||
const [hovered, setHovered] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.text_button_wrapper}
|
className={`${styles.text_button_wrapper} ${
|
||||||
onMouseEnter={() => setHovered(true)}
|
props.disabled ? styles.disabled : ""
|
||||||
onMouseLeave={() => setHovered(false)}
|
}`}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
<Icon
|
{!!props.iconName && (
|
||||||
name={props.iconName}
|
<Icon name={props.iconName} size="normal" color={props.color} />
|
||||||
size="normal"
|
)}
|
||||||
color={props.color}
|
<span className={styles[props.color]}>{props.label}</span>
|
||||||
hovered={hovered}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={`
|
|
||||||
${styles[props.color] ?? ""}
|
|
||||||
${hovered ? styles.hovered ?? "" : ""}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{props.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,7 @@ import { HiBuildingOffice } from "react-icons/hi2";
|
|||||||
import {
|
import {
|
||||||
IoIosAdd,
|
IoIosAdd,
|
||||||
IoIosHelpCircleOutline,
|
IoIosHelpCircleOutline,
|
||||||
|
IoIosNotifications,
|
||||||
IoIosRadio,
|
IoIosRadio,
|
||||||
IoMdClose,
|
IoMdClose,
|
||||||
IoMdLogOut,
|
IoMdLogOut,
|
||||||
@ -66,6 +67,8 @@ import {
|
|||||||
MdDynamicFeed,
|
MdDynamicFeed,
|
||||||
MdHistory,
|
MdHistory,
|
||||||
MdLink,
|
MdLink,
|
||||||
|
MdMarkEmailRead,
|
||||||
|
MdMarkEmailUnread,
|
||||||
MdOutlineModeEditOutline,
|
MdOutlineModeEditOutline,
|
||||||
MdUnfoldLess,
|
MdUnfoldLess,
|
||||||
MdUnfoldMore,
|
MdUnfoldMore,
|
||||||
@ -121,12 +124,14 @@ export const iconList: { [name: string]: IconType } = {
|
|||||||
loader: AiOutlineLoading3Quarters,
|
loader: AiOutlineLoading3Quarters,
|
||||||
logout: IoMdLogOut,
|
logout: IoMdLogOut,
|
||||||
moon: FaMoon,
|
moon: FaMoon,
|
||||||
|
notifications: IoIosNotifications,
|
||||||
office: HiBuildingOffice,
|
office: HiBuildingOffice,
|
||||||
options: SlOptions,
|
options: SlOptions,
|
||||||
paragraph: BsTextParagraph,
|
paragraph: BsTextParagraph,
|
||||||
prompt: FaRegKeyboard,
|
prompt: FaRegKeyboard,
|
||||||
redirection: BsArrowRightShort,
|
redirection: BsArrowRightShort,
|
||||||
radio: IoIosRadio,
|
radio: IoIosRadio,
|
||||||
|
read: MdMarkEmailRead,
|
||||||
robot: LiaRobotSolid,
|
robot: LiaRobotSolid,
|
||||||
search: LuSearch,
|
search: LuSearch,
|
||||||
settings: IoSettingsSharp,
|
settings: IoSettingsSharp,
|
||||||
@ -140,6 +145,7 @@ export const iconList: { [name: string]: IconType } = {
|
|||||||
twitter: FaTwitter,
|
twitter: FaTwitter,
|
||||||
unfold: MdUnfoldMore,
|
unfold: MdUnfoldMore,
|
||||||
unlock: FaUnlock,
|
unlock: FaUnlock,
|
||||||
|
unread: MdMarkEmailUnread,
|
||||||
upload: FiUpload,
|
upload: FiUpload,
|
||||||
uploadFile: MdUploadFile,
|
uploadFile: MdUploadFile,
|
||||||
user: FaRegUserCircle,
|
user: FaRegUserCircle,
|
||||||
|
@ -60,6 +60,8 @@ const ContentSecurityPolicy = {
|
|||||||
"connect-src": [
|
"connect-src": [
|
||||||
"'self'",
|
"'self'",
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL.replace("https", "wss"),
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL.replace("http", "ws"),
|
||||||
process.env.NEXT_PUBLIC_BACKEND_URL,
|
process.env.NEXT_PUBLIC_BACKEND_URL,
|
||||||
process.env.NEXT_PUBLIC_CMS_URL,
|
process.env.NEXT_PUBLIC_CMS_URL,
|
||||||
"*.intercom.io",
|
"*.intercom.io",
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$very_tiny: 10px;
|
||||||
$tiny: 12px;
|
$tiny: 12px;
|
||||||
$small: 14px;
|
$small: 14px;
|
||||||
$medium: 16px;
|
$medium: 16px;
|
||||||
|
Loading…
Reference in New Issue
Block a user