feat: update header and remove prompt / brain on backspace (#1052)

* feat: update header

* feat: remove selected prompt / brain on backspace

* feat(chat): update suggestions component

* refactor: add getAxiosErrorParams
This commit is contained in:
Mamadou DICKO 2023-08-29 15:52:22 +02:00 committed by GitHub
parent 6e43e6f16f
commit c5a7b8faef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 86 additions and 185 deletions

View File

@ -69,7 +69,7 @@ export const MentionInput = ({
// `open` should be directly passed to the MentionSuggestions component. // `open` should be directly passed to the MentionSuggestions component.
// However, it is not working as expected since we are not able to click on button in custom suggestion renderer. // However, it is not working as expected since we are not able to click on button in custom suggestion renderer.
// So, we are using this hack to make it work. // So, we are using this hack to make it work.
visibility: open ? "visible" : "hidden", opacity: open ? 1 : 0,
}} }}
> >
<MentionSuggestions <MentionSuggestions

View File

@ -21,7 +21,9 @@ export const useMentionPlugin = () => {
minWidth: "auto", minWidth: "auto",
backgroundColor: "transparent", backgroundColor: "transparent",
padding: "0", padding: "0",
marginBottom: "5", // We are adding a bottom margin to the suggestions container since it is overlapping with mention remove icon
// Please, do not remove!
marginBottom: "20px",
}; };
}, },
}, },

View File

@ -127,10 +127,21 @@ export const useMentionInput = ({
return getDefaultKeyBinding(e); return getDefaultKeyBinding(e);
} }
if ( if (e.key === "Backspace" || e.key === "Delete") {
(e.key === "Backspace" || e.key === "Delete") && if (!(getEditorText(editorState) === "")) {
getEditorText(editorState) === "" return getDefaultKeyBinding(e);
) { }
if (currentPromptId !== null) {
setCurrentPromptId(null);
return "backspace";
}
if (currentBrainId !== null) {
setCurrentBrainId(null);
return "backspace";
}
return "backspace"; return "backspace";
} }

View File

@ -7,7 +7,7 @@ import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSubscriptionApi } from "@/lib/api/subscription/useSubscriptionApi"; import { useSubscriptionApi } from "@/lib/api/subscription/useSubscriptionApi";
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useToast } from "@/lib/hooks"; import { useToast } from "@/lib/hooks";
import { useEventTracking } from "@/services/analytics/useEventTracking"; import { useEventTracking } from "@/services/analytics/useEventTracking";

View File

@ -2,7 +2,7 @@
import Link from "next/link"; import Link from "next/link";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
import Button from "@/lib/components/ui/Button"; import Button from "@/lib/components/ui/Button";
import { Divider } from "@/lib/components/ui/Divider"; import { Divider } from "@/lib/components/ui/Divider";
import PageHeading from "@/lib/components/ui/PageHeading"; import PageHeading from "@/lib/components/ui/PageHeading";

View File

@ -1,7 +1,7 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import { AxiosInstance } from "axios"; import { AxiosInstance } from "axios";
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
import { import {
BackendMinimalBrainForUser, BackendMinimalBrainForUser,
Brain, Brain,

View File

@ -1,4 +1,4 @@
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
export type SubscriptionUpdatableProperties = { export type SubscriptionUpdatableProperties = {
role: BrainRoleType | null; role: BrainRoleType | null;

View File

@ -1,4 +1,4 @@
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
import { Subscription } from "../brain"; import { Subscription } from "../brain";

View File

@ -1,4 +1,4 @@
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
import { SubscriptionUpdatableProperties } from "../types"; import { SubscriptionUpdatableProperties } from "../types";

View File

@ -1,7 +1,7 @@
import { AxiosInstance } from "axios"; import { AxiosInstance } from "axios";
import { UUID } from "crypto"; import { UUID } from "crypto";
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
export const acceptInvitation = async ( export const acceptInvitation = async (
brainId: UUID, brainId: UUID,

View File

@ -7,7 +7,7 @@ import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainConte
import { RemoveAccessIcon } from "./components/RemoveAccessIcon"; import { RemoveAccessIcon } from "./components/RemoveAccessIcon";
import { useBrainUser } from "./hooks/useBrainUser"; import { useBrainUser } from "./hooks/useBrainUser";
import { BrainRoleType } from "../../../NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "../../types";
type BrainUserProps = { type BrainUserProps = {
email: string; email: string;
role: BrainRoleType; role: BrainRoleType;

View File

@ -1,13 +1,12 @@
import axios, { AxiosResponse } from "axios";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
import { useBrainApi } from "@/lib/api/brain/useBrainApi"; import { useBrainApi } from "@/lib/api/brain/useBrainApi";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { getAxiosErrorParams } from "@/lib/helpers/getAxiosErrorParams";
import { useToast } from "@/lib/hooks"; import { useToast } from "@/lib/hooks";
import { BrainRoleType } from "../../../../NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "../../../types";
type UseBrainUserProps = { type UseBrainUserProps = {
fetchBrainUsers: () => Promise<void>; fetchBrainUsers: () => Promise<void>;
role: BrainRoleType; role: BrainRoleType;
@ -26,36 +25,41 @@ export const useBrainUser = ({
const [selectedRole, setSelectedRole] = useState<BrainRoleType>(role); const [selectedRole, setSelectedRole] = useState<BrainRoleType>(role);
const [isRemovingAccess, setIsRemovingAccess] = useState(false); const [isRemovingAccess, setIsRemovingAccess] = useState(false);
const { currentBrain } = useBrainContext(); const { currentBrain } = useBrainContext();
const { t } = useTranslation(['translation','brain']); const { t } = useTranslation(["translation", "brain"]);
const updateSelectedRole = async (newRole: BrainRoleType) => { const updateSelectedRole = async (newRole: BrainRoleType) => {
setSelectedRole(newRole); setSelectedRole(newRole);
try { try {
await updateBrainAccess(brainId, email, { await updateBrainAccess(brainId, email, {
role: newRole, role: newRole,
}); });
publish({ variant: "success", text: t('userRoleUpdated', { email: email, role: newRole, ns: 'brain' }) }); publish({
variant: "success",
text: t("userRoleUpdated", {
email: email,
role: newRole,
ns: "brain",
}),
});
void fetchBrainUsers(); void fetchBrainUsers();
} catch (e) { } catch (e) {
if (axios.isAxiosError(e) && e.response?.status === 403) { const axiosError = getAxiosErrorParams(e);
if (axiosError !== undefined && axiosError.status === 403) {
publish({ publish({
variant: "danger", variant: "danger",
text: `${JSON.stringify( text: axiosError.message,
(
e.response as {
data: { detail: string };
}
).data.detail
)}`,
}); });
} else { } else {
publish({ publish({
variant: "danger", variant: "danger",
text: t('userRoleUpdateFailed', { email: email, role: newRole, ns: 'brain' }) text: t("userRoleUpdateFailed", {
email: email,
role: newRole,
ns: "brain",
}),
}); });
} }
} }
}; };
const removeUserAccess = async () => { const removeUserAccess = async () => {
setIsRemovingAccess(true); setIsRemovingAccess(true);
try { try {
@ -64,23 +68,20 @@ export const useBrainUser = ({
}); });
publish({ publish({
variant: "success", variant: "success",
text: t('userRemoved', { email: email, ns: 'brain' }) text: t("userRemoved", { email: email, ns: "brain" }),
}); });
void fetchBrainUsers(); void fetchBrainUsers();
} catch (e) { } catch (e) {
if (axios.isAxiosError(e) && e.response?.data !== undefined) { const axiosError = getAxiosErrorParams(e);
if (axiosError !== undefined) {
publish({ publish({
variant: "danger", variant: "danger",
text: ( text: axiosError.message,
e.response as AxiosResponse<{
detail: string;
}>
).data.detail,
}); });
} else { } else {
publish({ publish({
variant: "danger", variant: "danger",
text: t('userRemoveFailed', { email: email, ns: 'brain' }) text: t("userRemoveFailed", { email: email, ns: "brain" }),
}); });
} }
} finally { } finally {
@ -94,6 +95,6 @@ export const useBrainUser = ({
removeUserAccess, removeUserAccess,
updateSelectedRole, updateSelectedRole,
selectedRole, selectedRole,
canRemoveAccess canRemoveAccess,
}; };
}; };

View File

@ -1,5 +1,5 @@
import Link from "next/link"; import Link from "next/link";
import { MdSettings } from "react-icons/md"; import { FaBrain } from "react-icons/fa";
import Button from "@/lib/components/ui/Button"; import Button from "@/lib/components/ui/Button";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
@ -14,7 +14,7 @@ export const BrainManagementButton = (): JSX.Element => {
className="focus:outline-none text-2xl" className="focus:outline-none text-2xl"
aria-label="Settings" aria-label="Settings"
> >
<MdSettings /> <FaBrain className="w-6 h-6" />
</Button> </Button>
</Link> </Link>
); );

View File

@ -1,89 +0,0 @@
import { useState } from "react";
import { useTranslation } from 'react-i18next'
import { FaBrain } from "react-icons/fa";
import { MdCheck } from "react-icons/md";
import Field from "@/lib/components/ui/Field";
import Popover from "@/lib/components/ui/Popover";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { BrainActions } from "./components/BrainActions/BrainActions";
import { AddBrainModal } from "../../../../../AddBrainModal/AddBrainModal";
export const BrainsDropDown = (): JSX.Element => {
const [searchQuery, setSearchQuery] = useState("");
const { allBrains, isFetchingBrains, setActiveBrain, currentBrain } =
useBrainContext();
const { t } = useTranslation(['translation','brain']);
return (
<>
{/* Add the brain icon and dropdown */}
<div className="relative ml-auto px-4 py-2">
<Popover
Trigger={
<button
type="button"
className="flex items-center focus:outline-none"
>
<FaBrain className="w-6 h-6" />
</button>
}
ActionTrigger={<AddBrainModal />}
CloseTrigger={false}
>
<div>
<Field
name="brainsearch"
placeholder= {t('searchBrain',{ns:'brain'})}
autoFocus
autoComplete="off"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<div className="overflow-auto scrollbar flex flex-col h-48 mt-5">
{/* List of brains */}
{isFetchingBrains && (
<div className="flex items-center justify-center h-full">
<p className="text-gray-500">{t('loading')}</p>
</div>
)}
{allBrains.map((brain) => {
if (brain.name.includes(searchQuery)) {
return (
<div
key={brain.id}
className="relative flex group items-center"
>
<button
type="button"
className={`flex flex-1 items-center gap-2 w-full text-left px-4 py-2 text-sm leading-5 text-gray-900 dark:text-gray-300 group-hover:bg-gray-100 dark:group-hover:bg-gray-700 group-focus:bg-gray-100 dark:group-focus:bg-gray-700 group-focus:outline-none transition-colors`}
onClick={() => {
setActiveBrain({ ...brain });
setSearchQuery("");
}}
>
<span>
<MdCheck
style={{
opacity: currentBrain?.id === brain.id ? 1 : 0,
}}
className="text-xl transition-opacity"
width={32}
height={32}
/>
</span>
<span className="flex-1">{brain.name}</span>
</button>
<BrainActions brain={brain} />
</div>
);
}
})}
</div>
</div>
</Popover>
</div>
</>
);
};

View File

@ -1,17 +0,0 @@
import { ShareBrain } from "@/lib/components/ShareBrain";
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
import { DeleteBrain } from "./components";
type BrainActionsProps = {
brain: MinimalBrainForUser;
};
export const BrainActions = ({ brain }: BrainActionsProps): JSX.Element => {
return (
<div className="absolute right-0 flex flex-row">
<ShareBrain brainId={brain.id} />
<DeleteBrain brainId={brain.id} />
</div>
);
};

View File

@ -1,19 +0,0 @@
import { UUID } from "crypto";
import { MdDelete } from "react-icons/md";
import Button from "@/lib/components/ui/Button";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
export const DeleteBrain = ({ brainId }: { brainId: UUID }): JSX.Element => {
const { deleteBrain } = useBrainContext();
return (
<Button
className="group-hover:visible invisible hover:text-red-500 transition-[colors,opacity] p-1"
onClick={() => void deleteBrain(brainId)}
variant={"tertiary"}
>
<MdDelete className="text-xl" />
</Button>
);
};

View File

@ -1,2 +0,0 @@
export * from "@/lib/components/AddBrainModal/AddBrainModal";
export * from "./BrainActions";

View File

@ -1 +0,0 @@
export * from "./BrainsDropDown";

View File

@ -9,7 +9,6 @@ import { cn } from "@/lib/utils";
import { AuthButtons } from "./components/AuthButtons"; import { AuthButtons } from "./components/AuthButtons";
import { BrainManagementButton } from "./components/BrainManagementButton"; import { BrainManagementButton } from "./components/BrainManagementButton";
import { BrainsDropDown } from "./components/BrainsDropDown";
import { DarkModeToggle } from "./components/DarkModeToggle"; import { DarkModeToggle } from "./components/DarkModeToggle";
import { LanguageDropDown } from "./components/LanguageDropDown"; import { LanguageDropDown } from "./components/LanguageDropDown";
import { NavLink } from "./components/NavLink"; import { NavLink } from "./components/NavLink";
@ -60,7 +59,6 @@ export const NavItems = ({
<div className="flex sm:flex-1 sm:justify-end flex-col items-center justify-center sm:flex-row gap-5 sm:gap-2"> <div className="flex sm:flex-1 sm:justify-end flex-col items-center justify-center sm:flex-row gap-5 sm:gap-2">
{isUserLoggedIn && ( {isUserLoggedIn && (
<> <>
<BrainsDropDown />
<BrainManagementButton /> <BrainManagementButton />
<Link aria-label="account" className="" href={"/user"}> <Link aria-label="account" className="" href={"/user"}>
<MdPerson className="text-2xl" /> <MdPerson className="text-2xl" />

View File

@ -14,7 +14,7 @@ import { Modal } from "@/lib/components/ui/Modal";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useShareBrain } from "@/lib/hooks/useShareBrain"; import { useShareBrain } from "@/lib/hooks/useShareBrain";
import { BrainRoleType } from "../NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "../BrainUsers/types";
type ShareBrainModalProps = { type ShareBrainModalProps = {
brainId: UUID; brainId: UUID;

View File

@ -1,4 +1,4 @@
import { BrainRoleType } from "../../NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "../../BrainUsers/types";
export type SelectOptionsProps = { export type SelectOptionsProps = {
label: string; label: string;

View File

@ -1,4 +1,4 @@
import { BrainRoleAssignation } from "../../NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleAssignation } from "../../BrainUsers/types";
export const generateBrainAssignation = (): BrainRoleAssignation => { export const generateBrainAssignation = (): BrainRoleAssignation => {
return { return {

View File

@ -6,10 +6,7 @@ import Field from "@/lib/components/ui/Field";
import { Select } from "@/lib/components/ui/Select"; import { Select } from "@/lib/components/ui/Select";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { import { BrainRoleAssignation, BrainRoleType } from "./BrainUsers/types";
BrainRoleAssignation,
BrainRoleType,
} from "./NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types";
import { userRoleToAssignableRoles } from "./ShareBrain/types"; import { userRoleToAssignableRoles } from "./ShareBrain/types";
type UserToInviteProps = { type UserToInviteProps = {

View File

@ -1,6 +1,6 @@
import { UUID } from "crypto"; import { UUID } from "crypto";
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; import { BrainRoleType } from "@/lib/components/BrainUsers/types";
import { Document } from "@/lib/types/Document"; import { Document } from "@/lib/types/Document";
import { useBrainProvider } from "./hooks/useBrainProvider"; import { useBrainProvider } from "./hooks/useBrainProvider";

View File

@ -0,0 +1,22 @@
import { AxiosResponse, isAxiosError } from "axios";
type AxiosErrorParams = {
message: string;
status: number;
};
export const getAxiosErrorParams = (
e: unknown
): AxiosErrorParams | undefined => {
if (isAxiosError(e) && e.response?.data !== undefined) {
return {
message: (
e.response as AxiosResponse<{
detail: string;
}>
).data.detail,
status: e.response.status,
};
}
return undefined;
};

View File

@ -10,7 +10,7 @@ import { useToast } from "@/lib/hooks";
import { import {
BrainRoleAssignation, BrainRoleAssignation,
BrainRoleType, BrainRoleType,
} from "../components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types"; } from "../components/BrainUsers/types";
import { generateBrainAssignation } from "../components/ShareBrain/utils/generateBrainAssignation"; import { generateBrainAssignation } from "../components/ShareBrain/utils/generateBrainAssignation";
import { useBrainContext } from "../context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "../context/BrainProvider/hooks/useBrainContext";