feat(frontend): design changes on user profile (#2140)

# 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:
Antoine Dewez 2024-02-03 17:45:05 -08:00 committed by GitHub
parent 7d871d9c34
commit 8f49a724ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 584 additions and 217 deletions

View File

@ -1,10 +1,10 @@
import React from "react";
import { CopyButton } from "@/lib/components/ui/CopyButton";
import Icon from "@/lib/components/ui/Icon/Icon";
import { Source } from "@/lib/types/MessageMetadata";
import styles from "./MessageRow.module.scss";
import { CopyButton } from "./components/CopyButton";
import { MessageContent } from "./components/MessageContent/MessageContent";
import { QuestionBrain } from "./components/QuestionBrain/QuestionBrain";
import { QuestionPrompt } from "./components/QuestionPrompt/QuestionPrompt";
@ -26,7 +26,7 @@ export const MessageRow = React.forwardRef(
{ speaker, text, brainName, promptName, children }: MessageRowProps,
ref: React.Ref<HTMLDivElement>
) => {
const { handleCopy, isCopied, isUserSpeaker } = useMessageRow({
const { handleCopy, isUserSpeaker } = useMessageRow({
speaker,
text,
});
@ -58,7 +58,7 @@ export const MessageRow = React.forwardRef(
<MessageContent text={messageContent} isUser={isUserSpeaker} />
{!isUserSpeaker && messageContent !== "🧠" && (
<div className={styles.copy_button}>
<CopyButton handleCopy={handleCopy} isCopied={isCopied} />
<CopyButton handleCopy={handleCopy} />
</div>
)}
</>

View File

@ -1,24 +0,0 @@
import Icon from "@/lib/components/ui/Icon/Icon";
type CopyButtonProps = {
handleCopy: () => void;
isCopied: boolean;
};
export const CopyButton = ({
handleCopy,
isCopied,
}: CopyButtonProps): JSX.Element => (
<button
className="text-gray-500 hover:text-gray-700 transition"
onClick={handleCopy}
title={isCopied ? "Copied!" : "Copy to clipboard"}
>
<Icon
name={isCopied ? "checkCircle" : "copy"}
color={isCopied ? "primary" : "black"}
size="small"
handleHover={true}
/>
</button>
);

View File

@ -1,5 +1,3 @@
import { useState } from "react";
type UseMessageRowProps = {
speaker: "user" | "assistant";
text?: string;
@ -8,22 +6,16 @@ type UseMessageRowProps = {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useMessageRow = ({ speaker, text }: UseMessageRowProps) => {
const isUserSpeaker = speaker === "user";
const [isCopied, setIsCopied] = useState(false);
const handleCopy = () => {
if (text === undefined) {
return;
}
navigator.clipboard.writeText(text).then(
() => setIsCopied(true),
(err) => console.error("Failed to copy!", err)
);
setTimeout(() => setIsCopied(false), 2000); // Reset after 2 seconds
navigator.clipboard.writeText(text).catch((err) => console.error(err));
};
return {
isUserSpeaker,
isCopied,
handleCopy,
};
};

View File

@ -0,0 +1,6 @@
@use "@/styles/Spacings.module.scss";
.response_wrapper {
display: flex;
gap: Spacings.$spacing03;
}

View File

@ -2,45 +2,35 @@
"use client";
import { useTranslation } from "react-i18next";
import { FaCopy } from "react-icons/fa";
import Button from "@/lib/components/ui/Button";
import Field from "@/lib/components/ui/Field";
import { CopyButton } from "@/lib/components/ui/CopyButton";
import { FieldHeader } from "@/lib/components/ui/FieldHeader/FieldHeader";
import styles from "./ApiKeyConfig.module.scss";
import { useApiKeyConfig } from "./hooks/useApiKeyConfig";
export const ApiKeyConfig = (): JSX.Element => {
const {
apiKey,
handleCopyClick,
handleCreateClick,
} = useApiKeyConfig();
const { apiKey, handleCopyClick, handleCreateClick } = useApiKeyConfig();
const { t } = useTranslation(["config"]);
return (
<>
<h3 className="font-semibold mb-2">Quivr {t("apiKey")}</h3>
<div>
{apiKey === "" ? (
<Button
data-testid="create-new-key"
variant="secondary"
onClick={() => void handleCreateClick()}
>
Create New Key
</Button>
) : (
<div className="flex items-center space-x-2">
<Field name="quivrApiKey" disabled={true} value={apiKey} />
<button data-testid="copy-api-key-button" onClick={handleCopyClick}>
<FaCopy />
</button>
</div>
)}
</div>
</>
<div>
<FieldHeader iconName="key" label={`Quivr ${t("apiKey")}`} />
{apiKey === "" ? (
<Button
data-testid="create-new-key"
variant="secondary"
onClick={() => void handleCreateClick()}
>
Create New Key
</Button>
) : (
<div className={styles.response_wrapper}>
<span>{apiKey}</span>
<CopyButton handleCopy={handleCopyClick} />
</div>
)}
</div>
);
};

View File

@ -63,10 +63,7 @@ export const useApiKeyConfig = () => {
try {
setChangeOpenAiApiKeyRequestPending(true);
await updateUserIdentity({
});
await updateUserIdentity({});
void queryClient.invalidateQueries({
queryKey: [USER_IDENTITY_DATA_KEY],
});
@ -85,8 +82,7 @@ export const useApiKeyConfig = () => {
const removeOpenAiApiKey = async () => {
try {
setChangeOpenAiApiKeyRequestPending(true);
await updateUserIdentity({
});
await updateUserIdentity({});
publish({
variant: "success",
@ -103,8 +99,6 @@ export const useApiKeyConfig = () => {
}
};
return {
handleCreateClick,
apiKey,

View File

@ -0,0 +1,5 @@
import { UserStatistics } from "../UserStatistics";
export const BrainsUsage = (): JSX.Element => {
return <UserStatistics />;
};

View File

@ -2,36 +2,25 @@
import { useTranslation } from "react-i18next";
import { CountrySelector } from "@/lib/components/ui/CountrySelector/CountrySelector";
import { useLanguageHook } from "./hooks/useLanguageHook";
const LanguageSelect = (): JSX.Element => {
const { t } = useTranslation(["translation"]);
const { allLanguages, currentLanguage, change } = useLanguageHook();
const { currentLanguage, change } = useLanguageHook();
if (!currentLanguage) {
return <></>;
}
return (
<fieldset name="language" className="mb-2">
<label
className="block text-slate-700 dark:text-slate-300 mb-2"
htmlFor="language"
>
{t("languageSelect")}
</label>
<select
data-testid="language-select"
name="language"
id="language"
value={currentLanguage}
onChange={(e) => change(e.target.value)}
className="bg-slate-50 focus-visible:ring-0 border rounded dark:bg-black dark:text-white p-2 w-full md:w-1/2 lg:w-1/3"
>
{Object.keys(allLanguages).map((lang) => (
<option data-testid={`option-${lang}`} value={lang} key={lang}>
{allLanguages[lang].label}
</option>
))}
</select>
</fieldset>
<CountrySelector
iconName="flag"
label={t("languageSelect")}
currentValue={currentLanguage}
setCurrentValue={change}
/>
);
};

View File

@ -3,58 +3,70 @@ import { useTranslation } from "react-i18next";
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
export const languages = {
en: {
label: "English",
},
es: {
label: "Español",
},
fr: {
label: "Français",
},
ptbr: {
label: "Português",
},
ru: {
label: "Русский",
},
zh_cn: {
label: "简体中文",
},
export type Language = {
label: string;
flag: string;
shortName: string;
};
export type Language = {
[key: string]: {
label: string;
};
};
export const languages: Language[] = [
{
label: "English",
flag: "🇬🇧",
shortName: "en",
},
{
label: "Español",
flag: "🇪🇸",
shortName: "es",
},
{
label: "Français",
flag: "🇫🇷",
shortName: "fr",
},
{
label: "Português",
flag: "🇵🇹",
shortName: "pt",
},
{
label: "Русский",
flag: "🇷🇺",
shortName: "ru",
},
{
label: "简体中文",
flag: "🇨🇳",
shortName: "zh",
},
];
export const useLanguageHook = (): {
change: (newLanguage: string) => void;
allLanguages: Language;
currentLanguage: string | undefined;
change: (newLanguage: Language) => void;
allLanguages: Language[];
currentLanguage: Language | undefined;
} => {
const { i18n } = useTranslation();
const [allLanguages, setAllLanguages] = useState<Language>({});
const [currentLanguage, setCurrentLanguage] = useState<string | undefined>();
const [allLanguages, setAllLanguages] = useState<Language[]>([]);
const [currentLanguage, setCurrentLanguage] = useState<Language>();
const { track } = useEventTracking();
useEffect(() => {
setAllLanguages(languages);
const savedLanguage = localStorage.getItem("selectedLanguage") ?? "English";
// get language from localStorage
const savedLanguage = localStorage.getItem("selectedLanguage") ?? "en";
setCurrentLanguage(savedLanguage);
setCurrentLanguage(
languages.find((language) => language.label === savedLanguage)
);
void i18n.changeLanguage(savedLanguage);
}, [i18n]);
const change = (newLanguage: string) => {
const change = (newLanguage: Language) => {
void track("CHANGE_LANGUAGE");
setCurrentLanguage(newLanguage);
localStorage.setItem("selectedLanguage", newLanguage);
void i18n.changeLanguage(newLanguage);
localStorage.setItem("selectedLanguage", newLanguage.label);
void i18n.changeLanguage(newLanguage.shortName);
};
return {

View File

@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next";
import Button from "@/lib/components/ui/Button";
import { Modal } from "@/lib/components/ui/Modal";
import TextButton from "@/lib/components/ui/TextButton/TextButton";
import { useLogoutModal } from "./hooks/useLogoutModal";
@ -17,9 +18,13 @@ export const LogoutModal = (): JSX.Element => {
return (
<Modal
Trigger={
<Button className="px-3 py-2" variant="secondary">
{t("logoutButton")}
</Button>
<div onClick={() => void 0}>
<TextButton
iconName="logout"
color="dangerous"
label={t("logoutButton")}
/>
</div>
}
isOpen={isLogoutModalOpened}
setOpen={setIsLogoutModalOpened}

View File

@ -0,0 +1,9 @@
import { StripePricingOrManageButton } from "../StripePricingOrManageButton";
export const Plan = (): JSX.Element => {
return (
<div>
<StripePricingOrManageButton />
</div>
);
};

View File

@ -0,0 +1,12 @@
@use "@/styles/Radius.module.scss";
@use "@/styles/Spacings.module.scss";
.settings_wrapper {
display: flex;
flex-direction: column;
gap: Spacings.$spacing07;
width: auto;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
border-radius: Radius.$big;
padding: Spacings.$spacing05;
}

View File

@ -0,0 +1,24 @@
import { InfoDisplayer } from "@/lib/components/ui/InfoDisplayer/InfoDisplayer";
import styles from "./Settings.module.scss";
import { ApiKeyConfig } from "../ApiKeyConfig";
import LanguageSelect from "../LanguageSelect/LanguageSelect";
import { LogoutModal } from "../LogoutModal/LogoutModal";
type InfoDisplayerProps = {
email: string;
};
export const Settings = ({ email }: InfoDisplayerProps): JSX.Element => {
return (
<div className={styles.settings_wrapper}>
<InfoDisplayer label="Email" iconName="email">
<span>{email}</span>
</InfoDisplayer>
<LanguageSelect />
<ApiKeyConfig />
<LogoutModal />
</div>
);
};

View File

@ -0,0 +1,45 @@
@use "@/styles/Colors.module.scss";
@use "@/styles/Radius.module.scss";
@use "@/styles/Spacings.module.scss";
@use "@/styles/ScreenSizes.module.scss";
@use "@/styles/Typography.module.scss";
.menu_card_container {
padding: Spacings.$spacing05;
border-radius: Radius.$big;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
cursor: pointer;
display: flex;
flex-direction: column;
gap: Spacings.$spacing03;
width: 20%;
@media (max-width: ScreenSizes.$small) {
width: auto;
.title,
.subtitle {
display: none;
}
}
&:hover,
&.selected {
background-color: Colors.$primary-lightest;
}
.first_line_wrapper {
display: flex;
align-items: center;
justify-content: space-between;
.title {
@include Typography.H2;
}
}
.subtitle {
font-size: Typography.$small;
color: Colors.$normal-grey;
}
}

View File

@ -0,0 +1,39 @@
import { useState } from "react";
import { Icon } from "@/lib/components/ui/Icon/Icon";
import styles from "./UserMenuCard.module.scss";
import { UserMenuCardProps } from "../types/types";
export const UserMenuCard = ({
title,
subtitle,
iconName,
selected,
onClick,
}: UserMenuCardProps): JSX.Element => {
const [isHovered, setIsHovered] = useState(false);
return (
<div
className={`
${styles.menu_card_container}
${selected ? styles.selected : ""}
`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={onClick}
>
<div className={styles.first_line_wrapper}>
<span className={styles.title}>{title}</span>
<Icon
name={iconName}
size="normal"
color={isHovered ? "primary" : "black"}
/>
</div>
<span className={styles.subtitle}>{subtitle}</span>
</div>
);
};

View File

@ -0,0 +1,7 @@
export type UserMenuCardProps = {
title: string;
subtitle: string;
iconName: string;
selected: boolean;
onClick?: () => void;
};

View File

@ -0,0 +1,13 @@
@use "@/styles/Spacings.module.scss";
.user_page_container {
padding: Spacings.$spacing09;
display: flex;
flex-direction: column;
gap: Spacings.$spacing09;
.left_menu_wrapper {
display: flex;
gap: Spacings.$spacing05;
}
}

View File

@ -1,86 +1,76 @@
"use client";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import Button from "@/lib/components/ui/Button";
import Card, { CardBody, CardHeader } from "@/lib/components/ui/Card";
import { useState } from "react";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { useUserData } from "@/lib/hooks/useUserData";
import { redirectToLogin } from "@/lib/router/redirectToLogin";
import { StripePricingOrManageButton, UserStatistics } from "./components";
import { ApiKeyConfig } from "./components/ApiKeyConfig";
import LanguageSelect from "./components/LanguageSelect/LanguageSelect";
import { LogoutModal } from "./components/LogoutCard/LogoutModal";
import { BrainsUsage } from "./components/BrainsUsage/BrainsUsage";
import { Plan } from "./components/Plan/Plan";
import { Settings } from "./components/Settings/Settings";
import { UserMenuCard } from "./components/UserMenuCard/UserMenuCard";
import { UserMenuCardProps } from "./components/types/types";
import styles from "./page.module.scss";
const UserPage = (): JSX.Element => {
const { session } = useSupabase();
const { userData } = useUserData();
if (!session) {
const [userMenuCards, setUserMenuCards] = useState<UserMenuCardProps[]>([
{
title: "Settings",
subtitle: "Change your settings",
iconName: "settings",
selected: true,
},
{
title: "Brain Usage",
subtitle: "View your brain usage",
iconName: "graph",
selected: false,
},
{
title: "Plan",
subtitle: "Manage your plan",
iconName: "unlock",
selected: false,
},
]);
const handleCardClick = (index: number) => {
setUserMenuCards(
userMenuCards.map((card, i) => ({
...card,
selected: i === index,
}))
);
};
if (!session || !userData) {
redirectToLogin();
}
const { user } = session;
const { t } = useTranslation(["translation", "user", "config", "chat"]);
return (
<>
<main className="container lg:w-2/3 mx-auto py-10 px-5">
<Link href="/search">
<Button className="mb-5" variant="primary">
{t("chat:back_to_search")}
</Button>
</Link>
<Card className="mb-5 shadow-sm hover:shadow-none">
<CardHeader>
<h2 className="font-bold text-xl">
{t("accountSection", { ns: "config" })}
</h2>
</CardHeader>
<CardBody className="flex flex-col items-stretch max-w-max gap-2">
<div className="flex gap-5 items-center">
<p>
<strong>{t("email")}:</strong> <span>{user.email}</span>
</p>
<LogoutModal />
</div>
<StripePricingOrManageButton />
</CardBody>
</Card>
<Card className="mb-5 shadow-sm hover:shadow-none">
<CardHeader>
<h2 className="font-bold text-xl">
{t("settings", { ns: "config" })}
</h2>
</CardHeader>
<CardBody>
<LanguageSelect />
</CardBody>
</Card>
<Card className="mb-5 shadow-sm hover:shadow-none">
<CardHeader>
<h2 className="font-bold text-xl">
{t("brainUsage", { ns: "user" })}
</h2>
</CardHeader>
<CardBody>
<UserStatistics />
</CardBody>
</Card>
<Card className="mb-5 shadow-sm hover:shadow-none">
<CardHeader>
<h2 className="font-bold text-xl">
{t("apiKey", { ns: "config" })}
</h2>
</CardHeader>
<CardBody className="p-3 flex flex-col">
<ApiKeyConfig />
</CardBody>
</Card>
<main className={styles.user_page_container}>
<div className={styles.left_menu_wrapper}>
{userMenuCards.map((card, index) => (
<UserMenuCard
key={index}
title={card.title}
subtitle={card.subtitle}
iconName={card.iconName}
selected={card.selected}
onClick={() => handleCardClick(index)}
/>
))}
</div>
<div className={styles.content_wrapper}>
{userMenuCards[0].selected && <Settings email={userData.email} />}
{userMenuCards[1].selected && <BrainsUsage />}
{userMenuCards[2].selected && <Plan />}
</div>
</main>
</>
);

View File

@ -37,6 +37,7 @@ export const Menu = (): JSX.Element => {
"/library",
"/brains-management",
"/search",
"/user",
];
const isMenuDisplayed = displayedOnPages.some((page) =>

View File

@ -10,8 +10,8 @@ export const DiscussionButton = (): JSX.Element => {
const { setIsVisible } = useSearchModalContext();
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
event.stopPropagation();
setIsVisible(true);
event.nativeEvent.stopImmediatePropagation();
};
return (

View File

@ -0,0 +1,43 @@
import { useEffect, useState } from "react";
import Icon from "@/lib/components/ui/Icon/Icon";
type CopyButtonProps = {
handleCopy: () => void;
};
export const CopyButton = ({ handleCopy }: CopyButtonProps): JSX.Element => {
const [isCopied, setIsCopied] = useState(false);
const handleClick = () => {
handleCopy();
setIsCopied(true);
};
useEffect(() => {
if (isCopied) {
const timer = setTimeout(() => {
setIsCopied(false);
}, 2000);
return () => {
clearTimeout(timer);
};
}
}, [isCopied]);
return (
<button
className="text-gray-500 hover:text-gray-700 transition"
onClick={handleClick}
title={isCopied ? "Copied!" : "Copy to clipboard"}
>
<Icon
name={isCopied ? "checkCircle" : "copy"}
color={isCopied ? "primary" : "black"}
size="small"
handleHover={true}
/>
</button>
);
};

View File

@ -0,0 +1,17 @@
@use "@/styles/Colors.module.scss";
@use "@/styles/Radius.module.scss";
.selection {
border-radius: Radius.$normal;
box-shadow: none;
cursor: pointer;
&:hover {
background-color: Colors.$lightest-black;
}
&:focus {
box-shadow: none;
border-color: Colors.$primary;
}
}

View File

@ -0,0 +1,54 @@
import {
Language,
useLanguageHook,
} from "@/app/user/components/LanguageSelect/hooks/useLanguageHook";
import styles from "./CountrySelector.module.scss";
import { FieldHeader } from "../FieldHeader/FieldHeader";
type CountrySelectorProps = {
iconName: string;
label: string;
currentValue: { label: string; flag: string };
setCurrentValue: (newCountry: Language) => void;
};
export const CountrySelector = ({
iconName,
label,
currentValue,
setCurrentValue,
}: CountrySelectorProps): JSX.Element => {
const { allLanguages } = useLanguageHook();
return (
<div className={styles.country_selector_container}>
<FieldHeader iconName={iconName} label={label} />
<select
className={styles.selection}
data-testid="language-select"
name="language"
id="language"
value={currentValue.label}
onChange={(e) =>
setCurrentValue(
allLanguages.find(
(language) => language.label === e.target.value
) ?? allLanguages[2]
)
}
>
{allLanguages.map((lang) => (
<option
data-testid={`option-${lang}`}
value={lang.label}
key={lang.shortName}
>
{lang.flag} {lang.label}
</option>
))}
</select>
</div>
);
};

View File

@ -0,0 +1,9 @@
@use "@/styles/Spacings.module.scss";
.field_header_wrapper {
display: flex;
gap: Spacings.$spacing03;
font-weight: bold;
align-items: center;
padding-bottom: Spacings.$spacing02;
}

View File

@ -0,0 +1,20 @@
import styles from "./FieldHeader.module.scss";
import { Icon } from "../Icon/Icon";
type FieldHeaderProps = {
iconName: string;
label: string;
};
export const FieldHeader = ({
iconName,
label,
}: FieldHeaderProps): JSX.Element => {
return (
<div className={styles.field_header_wrapper}>
<Icon name={iconName} color="black" size="small" />
<label>{label}</label>
</div>
);
};

View File

@ -53,6 +53,14 @@
}
}
.dangerous {
color: Colors.$dangerous;
&.hovered {
color: Colors.$dangerous-dark;
}
}
.disabled {
color: Colors.$black;
pointer-events: none;

View File

@ -40,11 +40,11 @@ export const Icon = ({
return (
<IconComponent
className={`
${classname ?? ""}
${styles[size] ?? ""}
${styles[color] ?? ""}
${disabled ? styles.disabled ?? "" : ""}
${iconHovered ? styles.hovered ?? "" : ""}
${classname}
${styles[size]}
${styles[color]}
${disabled ? styles.disabled : ""}
${iconHovered || hovered ? styles.hovered : ""}
`}
onMouseEnter={() => handleHover && setIconHovered(true)}
onMouseLeave={() => handleHover && setIconHovered(false)}

View File

@ -0,0 +1,6 @@
@use "@/styles/Spacings.module.scss";
.info_displayer_container {
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,22 @@
import styles from "./InfoDisplayer.module.scss";
import { FieldHeader } from "../FieldHeader/FieldHeader";
type InfoDisplayerProps = {
iconName: string;
label: string;
children: React.ReactNode;
};
export const InfoDisplayer = ({
iconName,
label,
children,
}: InfoDisplayerProps): JSX.Element => {
return (
<div className={styles.info_displayer_container}>
<FieldHeader iconName={iconName} label={label} />
{children}
</div>
);
};

View File

@ -16,3 +16,11 @@
color: Colors.$primary;
}
}
.dangerous {
color: Colors.$dangerous;
&.hovered {
color: Colors.$dangerous-dark;
}
}

View File

@ -0,0 +1,20 @@
@use "@/styles/Colors.module.scss";
@use "@/styles/Radius.module.scss";
@use "@/styles/Spacings.module.scss";
.text_input_container {
display: flex;
flex-direction: column;
gap: Spacings.$spacing03;
.text_input {
border: 1px solid Colors.$lighter-grey;
border-radius: Radius.$big;
padding: Spacings.$spacing03;
caret-color: Colors.$accent;
&:focus {
box-shadow: none;
}
}
}

View File

@ -0,0 +1,29 @@
import styles from "./TextInput.module.scss";
import { FieldHeader } from "../FieldHeader/FieldHeader";
type TextInputProps = {
iconName: string;
label: string;
inputValue: string;
setInputValue: (value: string) => void;
};
export const TextInput = ({
iconName,
label,
inputValue,
setInputValue,
}: TextInputProps): JSX.Element => {
return (
<div className={styles.text_input_container}>
<FieldHeader iconName={iconName} label={label} />
<input
className={styles.text_input}
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
</div>
);
};

View File

@ -1,14 +1,17 @@
import { AiOutlineLoading3Quarters } from "react-icons/ai";
import { BsArrowRightShort } from "react-icons/bs";
import { CiFlag1 } from "react-icons/ci";
import {
FaCheck,
FaCheckCircle,
FaKey,
FaRegStar,
FaRegUserCircle,
FaUnlock,
} from "react-icons/fa";
import { FaArrowUpFromBracket } from "react-icons/fa6";
import { IoIosAdd, IoMdClose } from "react-icons/io";
import { IoHomeOutline } from "react-icons/io5";
import { IoIosAdd, IoMdClose, IoMdLogOut } from "react-icons/io";
import { IoHomeOutline, IoSettingsSharp } from "react-icons/io5";
import { IconType } from "react-icons/lib";
import {
LuBrain,
@ -20,8 +23,15 @@ import {
LuPlusCircle,
LuSearch,
} from "react-icons/lu";
import { MdDelete, MdEdit, MdHistory, MdUploadFile } from "react-icons/md";
import {
MdAlternateEmail,
MdDelete,
MdEdit,
MdHistory,
MdUploadFile,
} from "react-icons/md";
import { RiHashtag } from "react-icons/ri";
import { VscGraph } from "react-icons/vsc";
export const iconList: { [name: string]: IconType } = {
add: LuPlusCircle,
@ -36,15 +46,22 @@ export const iconList: { [name: string]: IconType } = {
copy: LuCopy,
delete: MdDelete,
edit: MdEdit,
email: MdAlternateEmail,
file: LuFile,
flag: CiFlag1,
followUp: FaArrowUpFromBracket,
graph: VscGraph,
hastag: RiHashtag,
history: MdHistory,
home: IoHomeOutline,
key: FaKey,
loader: AiOutlineLoading3Quarters,
logout: IoMdLogOut,
redirection: BsArrowRightShort,
search: LuSearch,
settings: IoSettingsSharp,
star: FaRegStar,
unlock: FaUnlock,
upload: MdUploadFile,
user: FaRegUserCircle,
};

View File

@ -4,4 +4,5 @@ export type Color =
| "primary"
| "gold"
| "accent"
| "white";
| "white"
| "dangerous";

View File

@ -26,3 +26,7 @@ $dark-grey: #707070;
// GOLD
$gold: #b8860b;
// ERROR
$dangerous-dark: #e30c17;
$dangerous: #9b373c;