mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-15 01:21:48 +03:00
feat(frontend): onboarding form (#2342)
# Description TODO BEFORE RELEASE - [ ] Onboarded true for all existing users ## 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): --------- Co-authored-by: Stan Girard <girard.stanislas@gmail.com>
This commit is contained in:
parent
1381a0729e
commit
4aeb00fd47
@ -8,3 +8,6 @@ class UserUpdatableProperties(BaseModel):
|
||||
username: Optional[str] = None
|
||||
company: Optional[str] = None
|
||||
onboarded: Optional[bool] = None
|
||||
company_size: Optional[str] = None
|
||||
usage_purpose: Optional[str] = None
|
||||
|
||||
|
@ -10,3 +10,5 @@ class UserIdentity(BaseModel):
|
||||
username: Optional[str] = None
|
||||
company: Optional[str] = None
|
||||
onboarded: Optional[bool] = None
|
||||
company_size: Optional[str] = None
|
||||
usage_purpose: Optional[str] = None
|
||||
|
@ -18,6 +18,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 { OnboardingProvider } from "@/lib/context/OnboardingProvider/Onboarding-provider";
|
||||
import { SearchModalProvider } from "@/lib/context/SearchModalProvider/search-modal-provider";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { UserSettingsProvider } from "@/lib/context/UserSettingsProvider/User-settings.provider";
|
||||
@ -96,11 +97,13 @@ const AppWithQueryClient = ({ children }: PropsWithChildren): JSX.Element => {
|
||||
<KnowledgeToFeedProvider>
|
||||
<BrainCreationProvider>
|
||||
<MenuProvider>
|
||||
<ChatsProvider>
|
||||
<ChatProvider>
|
||||
<App>{children}</App>
|
||||
</ChatProvider>
|
||||
</ChatsProvider>
|
||||
<OnboardingProvider>
|
||||
<ChatsProvider>
|
||||
<ChatProvider>
|
||||
<App>{children}</App>
|
||||
</ChatProvider>
|
||||
</ChatsProvider>
|
||||
</OnboardingProvider>
|
||||
</MenuProvider>
|
||||
</BrainCreationProvider>
|
||||
</KnowledgeToFeedProvider>
|
||||
|
@ -5,6 +5,7 @@ import { useEffect } from "react";
|
||||
import { QuivrLogo } from "@/lib/assets/QuivrLogo";
|
||||
import { AddBrainModal } from "@/lib/components/AddBrainModal";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider";
|
||||
import { OnboardingModal } from "@/lib/components/OnboardingModal/OnboardingModal";
|
||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
||||
import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar";
|
||||
@ -66,6 +67,7 @@ const Search = (): JSX.Element => {
|
||||
</div>
|
||||
<UploadDocumentModal />
|
||||
<AddBrainModal />
|
||||
<OnboardingModal />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ export const ModelSelection = (props: ModelSelectionProps): JSX.Element => {
|
||||
void handleSubmit(false);
|
||||
}}
|
||||
selectedOption={{ value: model, label: model }}
|
||||
placeholder="hey"
|
||||
placeholder="Select a model"
|
||||
iconName="robot"
|
||||
/>
|
||||
</fieldset>
|
||||
|
@ -63,7 +63,10 @@ export const useApiKeyConfig = () => {
|
||||
try {
|
||||
setChangeOpenAiApiKeyRequestPending(true);
|
||||
|
||||
await updateUserIdentity({});
|
||||
await updateUserIdentity({
|
||||
username: userIdentity?.username ?? "",
|
||||
onboarded: userIdentity?.onboarded ?? false,
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [USER_IDENTITY_DATA_KEY],
|
||||
});
|
||||
@ -82,7 +85,10 @@ export const useApiKeyConfig = () => {
|
||||
const removeOpenAiApiKey = async () => {
|
||||
try {
|
||||
setChangeOpenAiApiKeyRequestPending(true);
|
||||
await updateUserIdentity({});
|
||||
await updateUserIdentity({
|
||||
username: userIdentity?.username ?? "",
|
||||
onboarded: userIdentity?.onboarded ?? false,
|
||||
});
|
||||
|
||||
publish({
|
||||
variant: "success",
|
||||
|
@ -3,12 +3,37 @@ import { UUID } from "crypto";
|
||||
|
||||
import { UserStats } from "@/lib/types/User";
|
||||
|
||||
export enum CompanySize {
|
||||
One = "1-10",
|
||||
Two = "10-25",
|
||||
Three = "25-50",
|
||||
Four = "50-100",
|
||||
Five = "100-500",
|
||||
Six = "500-1000",
|
||||
Seven = "1000-5000",
|
||||
Eight = "+5000",
|
||||
}
|
||||
|
||||
export enum UsagePurpose {
|
||||
Business = "Business",
|
||||
NGO = "NGO",
|
||||
Personal = "Personal",
|
||||
Student = "Student",
|
||||
Teacher = "Teacher",
|
||||
}
|
||||
|
||||
export type UserIdentityUpdatableProperties = {
|
||||
empty?: string | null;
|
||||
username: string;
|
||||
company?: string;
|
||||
onboarded: boolean;
|
||||
company_size?: CompanySize;
|
||||
usage_purpose?: UsagePurpose;
|
||||
};
|
||||
|
||||
export type UserIdentity = {
|
||||
user_id: UUID;
|
||||
onboarded: boolean;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export const updateUserIdentity = async (
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types";
|
||||
import { FieldHeader } from "@/lib/components/ui/FieldHeader/FieldHeader";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
import { TextAreaInput } from "@/lib/components/ui/TextAreaInput/TextAreaInput";
|
||||
import { TextInput } from "@/lib/components/ui/TextInput/TextInput";
|
||||
@ -35,26 +36,36 @@ export const BrainMainInfosStep = (): JSX.Element => {
|
||||
<div className={styles.brain_main_infos_wrapper}>
|
||||
<div className={styles.inputs_wrapper}>
|
||||
<span className={styles.title}>Define brain identity</span>
|
||||
<Controller
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
label="Name"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<TextAreaInput
|
||||
label="Description"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<FieldHeader iconName="brain" label="Name" mandatory={true} />
|
||||
<Controller
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
label="Enter your brain name"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldHeader
|
||||
iconName="paragraph"
|
||||
label="Description"
|
||||
mandatory={true}
|
||||
/>
|
||||
<Controller
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<TextAreaInput
|
||||
label="Enter your brain description"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.buttons_wrapper}>
|
||||
<QuivrButton
|
||||
|
@ -1,16 +1,25 @@
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { MenuButton } from "@/lib/components/Menu/components/MenuButton/MenuButton";
|
||||
import { useUserData } from "@/lib/hooks/useUserData";
|
||||
|
||||
export const ProfileButton = (): JSX.Element => {
|
||||
const pathname = usePathname() ?? "";
|
||||
const isSelected = pathname.includes("/user");
|
||||
const { userIdentityData } = useUserData();
|
||||
|
||||
let username = userIdentityData?.username ?? "Profile";
|
||||
|
||||
useEffect(() => {
|
||||
username = userIdentityData?.username ?? "Profile";
|
||||
}, [userIdentityData]);
|
||||
|
||||
return (
|
||||
<Link href="/user">
|
||||
<MenuButton
|
||||
label="Profile"
|
||||
label={username}
|
||||
iconName="user"
|
||||
type="open"
|
||||
isSelected={isSelected}
|
||||
|
@ -0,0 +1,21 @@
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
|
||||
.modal_content_wrapper {
|
||||
height: 100%;
|
||||
padding: Spacings.$spacing05;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.form_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
}
|
||||
|
||||
.button_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
154
frontend/lib/components/OnboardingModal/OnboardingModal.tsx
Normal file
154
frontend/lib/components/OnboardingModal/OnboardingModal.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
import { useUserApi } from "@/lib/api/user/useUserApi";
|
||||
import { CompanySize, UsagePurpose } from "@/lib/api/user/user";
|
||||
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||
import { useOnboardingContext } from "@/lib/context/OnboardingProvider/hooks/useOnboardingContext";
|
||||
|
||||
import styles from "./OnboardingModal.module.scss";
|
||||
|
||||
import { OnboardingProps } from "../OnboardingModal/types/types";
|
||||
import { FieldHeader } from "../ui/FieldHeader/FieldHeader";
|
||||
import { QuivrButton } from "../ui/QuivrButton/QuivrButton";
|
||||
import { SingleSelector } from "../ui/SingleSelector/SingleSelector";
|
||||
import { TextInput } from "../ui/TextInput/TextInput";
|
||||
|
||||
export const OnboardingModal = (): JSX.Element => {
|
||||
const { isOnboardingModalOpened, setIsOnboardingModalOpened } =
|
||||
useOnboardingContext();
|
||||
|
||||
const methods = useForm<OnboardingProps>({
|
||||
defaultValues: {
|
||||
username: "",
|
||||
companyName: "",
|
||||
companySize: undefined,
|
||||
usagePurpose: "",
|
||||
},
|
||||
});
|
||||
const { watch } = methods;
|
||||
const username = watch("username");
|
||||
|
||||
const { updateUserIdentity } = useUserApi();
|
||||
|
||||
const companySizeOptions = Object.entries(CompanySize).map(([, value]) => ({
|
||||
label: value,
|
||||
value: value,
|
||||
}));
|
||||
|
||||
const usagePurposeOptions = Object.entries(UsagePurpose).map(
|
||||
([key, value]) => ({
|
||||
label: value,
|
||||
value: key,
|
||||
})
|
||||
);
|
||||
|
||||
const submitForm = async () => {
|
||||
await updateUserIdentity({
|
||||
username: methods.getValues("username"),
|
||||
company: methods.getValues("companyName"),
|
||||
onboarded: true,
|
||||
company_size: methods.getValues("companySize"),
|
||||
usage_purpose: methods.getValues("usagePurpose") as
|
||||
| UsagePurpose
|
||||
| undefined,
|
||||
});
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<Modal
|
||||
title="Welcome to Quivr!"
|
||||
desc="Let us know a bit more about you to get started."
|
||||
isOpen={isOnboardingModalOpened}
|
||||
setOpen={setIsOnboardingModalOpened}
|
||||
CloseTrigger={<div />}
|
||||
unclosable={true}
|
||||
>
|
||||
<div className={styles.modal_content_wrapper}>
|
||||
<div className={styles.form_wrapper}>
|
||||
<div>
|
||||
<FieldHeader iconName="user" label="Username" mandatory={true} />
|
||||
<Controller
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
label="Choose a username"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldHeader iconName="office" label="Company" />
|
||||
<Controller
|
||||
name="companyName"
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
label="Your company name"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldHeader iconName="goal" label="Usage Purpose" />
|
||||
<Controller
|
||||
name="usagePurpose"
|
||||
render={({ field }) => (
|
||||
<SingleSelector
|
||||
iconName="goal"
|
||||
options={usagePurposeOptions}
|
||||
placeholder="In what context will you be using Quivr"
|
||||
selectedOption={
|
||||
field.value
|
||||
? {
|
||||
label: field.value as string,
|
||||
value: field.value as string,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldHeader iconName="hashtag" label="Size of your company" />
|
||||
<Controller
|
||||
name="companySize"
|
||||
render={({ field }) => (
|
||||
<SingleSelector
|
||||
iconName="hashtag"
|
||||
options={companySizeOptions}
|
||||
placeholder="Number of employees in your company"
|
||||
selectedOption={
|
||||
field.value
|
||||
? {
|
||||
label: field.value as string,
|
||||
value: field.value as string,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.button_wrapper}>
|
||||
<QuivrButton
|
||||
iconName="chevronRight"
|
||||
label="Submit"
|
||||
color="primary"
|
||||
onClick={() => submitForm()}
|
||||
disabled={!username}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
8
frontend/lib/components/OnboardingModal/types/types.ts
Normal file
8
frontend/lib/components/OnboardingModal/types/types.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { CompanySize } from "@/lib/api/user/user";
|
||||
|
||||
export type OnboardingProps = {
|
||||
username: string;
|
||||
companyName: string;
|
||||
companySize: CompanySize;
|
||||
usagePurpose: string;
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
|
||||
.field_header_wrapper {
|
||||
@ -6,4 +7,8 @@
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
padding-bottom: Spacings.$spacing02;
|
||||
|
||||
.mandatory {
|
||||
color: Colors.$dangerous;
|
||||
}
|
||||
}
|
||||
|
@ -8,17 +8,21 @@ type FieldHeaderProps = {
|
||||
iconName: string;
|
||||
label: string;
|
||||
help?: string;
|
||||
mandatory?: boolean;
|
||||
};
|
||||
|
||||
export const FieldHeader = ({
|
||||
iconName,
|
||||
label,
|
||||
help,
|
||||
mandatory,
|
||||
}: FieldHeaderProps): JSX.Element => {
|
||||
return (
|
||||
<div className={styles.field_header_wrapper}>
|
||||
<Icon name={iconName} color="black" size="small" />
|
||||
<label>{label}</label>
|
||||
{mandatory && <span className={styles.mandatory}>*</span>}
|
||||
<span className={styles.mandatory}>{help && "*"}</span>
|
||||
{help && (
|
||||
<Tooltip tooltip={help}>
|
||||
<div>
|
||||
|
@ -24,6 +24,8 @@
|
||||
box-shadow: BoxShadow.$medium;
|
||||
max-width: 90vw;
|
||||
overflow: scroll;
|
||||
width: 35vw;
|
||||
height: 80vh;
|
||||
|
||||
&.big_modal {
|
||||
width: 40vw;
|
||||
@ -36,9 +38,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: ScreenSizes.$small) {
|
||||
&.big_modal {
|
||||
width: 90vw;
|
||||
}
|
||||
width: 90vw;
|
||||
}
|
||||
|
||||
.close_button_wrapper {
|
||||
|
@ -20,6 +20,7 @@ type CommonModalProps = {
|
||||
isOpen?: undefined;
|
||||
setOpen?: undefined;
|
||||
bigModal?: boolean;
|
||||
unclosable?: boolean;
|
||||
unforceWhite?: boolean;
|
||||
};
|
||||
|
||||
@ -30,6 +31,31 @@ type ModalProps =
|
||||
setOpen: (isOpen: boolean) => void;
|
||||
});
|
||||
|
||||
const handleInteractOutside = (unclosable: boolean, event: Event) => {
|
||||
if (unclosable) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalContentAnimation = (
|
||||
isOpen: boolean,
|
||||
bigModal: boolean,
|
||||
unforceWhite: boolean
|
||||
) => {
|
||||
const initialAnimation = { opacity: 0, y: "-40%" };
|
||||
const animateAnimation = { opacity: 1, y: "0%" };
|
||||
const exitAnimation = { opacity: 0, y: "40%" };
|
||||
|
||||
return {
|
||||
initial: initialAnimation,
|
||||
animate: animateAnimation,
|
||||
exit: exitAnimation,
|
||||
className: `${styles.modal_content_wrapper} ${
|
||||
bigModal ? styles.big_modal : ""
|
||||
} ${unforceWhite ? styles.white : ""}`,
|
||||
};
|
||||
};
|
||||
|
||||
export const Modal = ({
|
||||
title,
|
||||
desc,
|
||||
@ -39,6 +65,7 @@ export const Modal = ({
|
||||
isOpen: customIsOpen,
|
||||
setOpen: customSetOpen,
|
||||
bigModal,
|
||||
unclosable,
|
||||
unforceWhite,
|
||||
}: ModalProps): JSX.Element => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
@ -62,14 +89,19 @@ export const Modal = ({
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<Dialog.Content asChild forceMount>
|
||||
<Dialog.Content
|
||||
asChild
|
||||
forceMount
|
||||
onInteractOutside={(event) =>
|
||||
handleInteractOutside(!!unclosable, event)
|
||||
}
|
||||
>
|
||||
<motion.div
|
||||
className={`${styles.modal_content_wrapper} ${
|
||||
bigModal ? styles.big_modal : ""
|
||||
} ${unforceWhite ? styles.white : ""}`}
|
||||
initial={{ opacity: 0, y: "-40%" }}
|
||||
animate={{ opacity: 1, y: "0%" }}
|
||||
exit={{ opacity: 0, y: "40%" }}
|
||||
{...handleModalContentAnimation(
|
||||
customIsOpen ?? isOpen,
|
||||
!!bigModal,
|
||||
!!unforceWhite
|
||||
)}
|
||||
>
|
||||
<Dialog.Title
|
||||
className="m-0 text-2xl font-bold"
|
||||
@ -93,14 +125,16 @@ export const Modal = ({
|
||||
</Button>
|
||||
)}
|
||||
</Dialog.Close>
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
className={styles.close_button_wrapper}
|
||||
aria-label="Close"
|
||||
>
|
||||
<MdClose />
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
{!unclosable && (
|
||||
<Dialog.Close asChild>
|
||||
<button
|
||||
className={styles.close_button_wrapper}
|
||||
aria-label="Close"
|
||||
>
|
||||
<MdClose />
|
||||
</button>
|
||||
</Dialog.Close>
|
||||
)}
|
||||
</motion.div>
|
||||
</Dialog.Content>
|
||||
</motion.div>
|
||||
|
@ -0,0 +1,36 @@
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
|
||||
import { useUserData } from "@/lib/hooks/useUserData";
|
||||
|
||||
export type OnboardingContextType = {
|
||||
isOnboardingModalOpened: boolean;
|
||||
setIsOnboardingModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const OnboardingContext = createContext<
|
||||
OnboardingContextType | undefined
|
||||
>(undefined);
|
||||
|
||||
export const OnboardingProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element => {
|
||||
const [isOnboardingModalOpened, setIsOnboardingModalOpened] = useState(false);
|
||||
const { userIdentityData } = useUserData();
|
||||
|
||||
useEffect(() => {
|
||||
setIsOnboardingModalOpened(!!userIdentityData?.onboarded);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OnboardingContext.Provider
|
||||
value={{
|
||||
isOnboardingModalOpened,
|
||||
setIsOnboardingModalOpened,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</OnboardingContext.Provider>
|
||||
);
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
import { useContext } from "react";
|
||||
|
||||
import {
|
||||
OnboardingContext,
|
||||
OnboardingContextType,
|
||||
} from "../Onboarding-provider";
|
||||
|
||||
export const useOnboardingContext = (): OnboardingContextType => {
|
||||
const context = useContext(OnboardingContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useOnboardingContext must be used within a OnboardingProvider"
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
@ -27,9 +27,11 @@ import {
|
||||
} from "react-icons/fa";
|
||||
import { FaInfo } from "react-icons/fa6";
|
||||
import { FiUpload } from "react-icons/fi";
|
||||
import { HiBuildingOffice } from "react-icons/hi2";
|
||||
import {
|
||||
IoIosAdd,
|
||||
IoIosHelpCircleOutline,
|
||||
IoIosRadio,
|
||||
IoMdClose,
|
||||
IoMdLogOut,
|
||||
} from "react-icons/io";
|
||||
@ -50,6 +52,7 @@ import {
|
||||
LuChevronLeft,
|
||||
LuChevronRight,
|
||||
LuCopy,
|
||||
LuGoal,
|
||||
LuPlusCircle,
|
||||
LuSearch,
|
||||
} from "react-icons/lu";
|
||||
@ -63,6 +66,7 @@ import {
|
||||
MdOutlineModeEditOutline,
|
||||
MdUploadFile,
|
||||
} from "react-icons/md";
|
||||
import { PiOfficeChairFill } from "react-icons/pi";
|
||||
import { RiHashtag } from "react-icons/ri";
|
||||
import { SlOptions } from "react-icons/sl";
|
||||
import { TbNetwork } from "react-icons/tb";
|
||||
@ -73,6 +77,7 @@ export const iconList: { [name: string]: IconType } = {
|
||||
addWithoutCircle: IoIosAdd,
|
||||
brain: LuBrain,
|
||||
brainCircuit: LuBrainCircuit,
|
||||
chair: PiOfficeChairFill,
|
||||
chat: BsChatLeftText,
|
||||
check: FaCheck,
|
||||
checkCircle: FaCheckCircle,
|
||||
@ -93,6 +98,7 @@ export const iconList: { [name: string]: IconType } = {
|
||||
flag: CiFlag1,
|
||||
followUp: IoArrowUpCircleOutline,
|
||||
github: FaGithub,
|
||||
goal: LuGoal,
|
||||
graph: VscGraph,
|
||||
hashtag: RiHashtag,
|
||||
help: IoIosHelpCircleOutline,
|
||||
@ -105,10 +111,12 @@ export const iconList: { [name: string]: IconType } = {
|
||||
loader: AiOutlineLoading3Quarters,
|
||||
logout: IoMdLogOut,
|
||||
moon: FaMoon,
|
||||
office: HiBuildingOffice,
|
||||
options: SlOptions,
|
||||
paragraph: BsTextParagraph,
|
||||
prompt: FaRegKeyboard,
|
||||
redirection: BsArrowRightShort,
|
||||
radio: IoIosRadio,
|
||||
robot: LiaRobotSolid,
|
||||
search: LuSearch,
|
||||
settings: IoSettingsSharp,
|
||||
|
@ -1,18 +1,25 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { USER_DATA_KEY } from "../api/user/config";
|
||||
import { USER_DATA_KEY, USER_IDENTITY_DATA_KEY } from "../api/user/config";
|
||||
import { useUserApi } from "../api/user/useUserApi";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useUserData = () => {
|
||||
const { getUser } = useUserApi();
|
||||
const { getUserIdentity } = useUserApi();
|
||||
|
||||
const { data: userData } = useQuery({
|
||||
queryKey: [USER_DATA_KEY],
|
||||
queryFn: getUser,
|
||||
});
|
||||
|
||||
const { data: userIdentityData } = useQuery({
|
||||
queryKey: [USER_IDENTITY_DATA_KEY],
|
||||
queryFn: getUserIdentity,
|
||||
});
|
||||
|
||||
return {
|
||||
userData,
|
||||
userIdentityData,
|
||||
};
|
||||
};
|
||||
|
@ -0,0 +1,7 @@
|
||||
create type "public"."user_identity_company_size" as enum ('1-10', '10-25', '25-50', '50-100', '100-250', '250-500', '500-1000', '1000-5000', '+5000');
|
||||
|
||||
alter table "public"."user_identity" add column "company_size" user_identity_company_size;
|
||||
|
||||
alter table "public"."user_identity" add column "role_in_company" text;
|
||||
|
||||
|
5
supabase/migrations/20240316195514_usage_purpose.sql
Normal file
5
supabase/migrations/20240316195514_usage_purpose.sql
Normal file
@ -0,0 +1,5 @@
|
||||
alter table "public"."user_identity" drop column "role_in_company";
|
||||
|
||||
alter table "public"."user_identity" add column "usage_purpose" text;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user