From ec561fcd7f8e49e46ef5aad8f4fe16ec5cb4003d Mon Sep 17 00:00:00 2001 From: Danny Avila <110412045+danny-avila@users.noreply.github.com> Date: Fri, 19 May 2023 16:02:41 -0400 Subject: [PATCH] Fixes all Nav Menu related errors and bugs (#331) * chore(client): update lucide-react package to version 0.220.0 style(client): change color of MessageHeader component text to gray-500 style(client): change color of nav-close-button to gray-400 and nav-open-button to gray-500 feat(client): add Panel component to replace svg icons in Nav component * fix: forwardRef errors in Nav Menu * refactor(SearchBar.jsx): change clearSearch prop destructuring to props destructuring refactor(SearchBar.jsx): add ref prop to SearchBar component refactor(getIcon.jsx): remove unused imports refactor(getIcon.jsx): add nullish coalescing operator to user.name and user.avatar properties * fix (NavLinks): modals no longer close on nav menu close * style(ExportModel.jsx): remove unnecessary z-index property from a div element * style(ExportModel.jsx): remove trailing whitespace in input element * refactor(Message.jsx): remove unused cancelled variable fix(Message.jsx): fix error message length exceeding 512 characters refactor(MenuItem.jsx): remove unused MenuItem component --- client/package.json | 2 +- client/src/components/Messages/Message.jsx | 9 +- .../src/components/Messages/MessageHeader.jsx | 2 +- client/src/components/Nav/ClearConvos.jsx | 17 +- client/src/components/Nav/DarkMode.jsx | 8 +- .../Nav/ExportConversation/ExportModel.jsx | 4 +- .../Nav/ExportConversation/index.jsx | 8 +- client/src/components/Nav/Logout.jsx | 8 +- client/src/components/Nav/NavLink.jsx | 25 +-- client/src/components/Nav/NavLinks.jsx | 151 +++++++++++------- client/src/components/Nav/SearchBar.jsx | 13 +- client/src/components/Nav/index.jsx | 50 +----- client/src/components/svg/Panel.jsx | 47 ++++++ client/src/components/ui/DialogTemplate.jsx | 28 ++-- client/src/utils/getIcon.jsx | 10 +- package-lock.json | 13 +- 16 files changed, 230 insertions(+), 165 deletions(-) create mode 100644 client/src/components/svg/Panel.jsx diff --git a/client/package.json b/client/package.json index 4fcf2f4a9..5d9a60fd7 100644 --- a/client/package.json +++ b/client/package.json @@ -53,7 +53,7 @@ "filenamify": "^6.0.0", "html2canvas": "^1.4.1", "lodash": "^4.17.21", - "lucide-react": "^0.113.0", + "lucide-react": "^0.220.0", "pino": "^8.12.1", "rc-input-number": "^7.4.2", "react": "^18.2.0", diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 223804f5d..613e28219 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import copy from 'copy-to-clipboard'; import SubRow from './Content/SubRow'; @@ -31,7 +31,7 @@ export default function Message({ siblingCount, setSiblingIdx }) { - const { text, searchResult, isCreatedByUser, error, submitting, unfinished, cancelled } = message; + const { text, searchResult, isCreatedByUser, error, submitting, unfinished } = message; const isSubmitting = useRecoilValue(store.isSubmitting); const setLatestMessage = useSetRecoilState(store.latestMessage); const [abortScroll, setAbort] = useState(false); @@ -74,6 +74,7 @@ export default function Message({ }; const getError = (text) => { + const errorMessage = text.length > 512 ? text.slice(0, 512) + '...' : text; const match = text.match(/\{[^{}]*\}/); var json = match ? match[0] : ''; if (isJson(json)) { @@ -83,10 +84,10 @@ export default function Message({ } else if (json.type === 'insufficient_quota') { return "We're sorry, but the default API key has reached its limit. To continue using this service, please set up your own API key. You can do this by clicking on the model logo in the top-left corner of the textbox."; } else { - return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`; + return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`; } } else { - return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${text}`; + return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`; } }; diff --git a/client/src/components/Messages/MessageHeader.jsx b/client/src/components/Messages/MessageHeader.jsx index a907cefbe..238ca6e01 100644 --- a/client/src/components/Messages/MessageHeader.jsx +++ b/client/src/components/Messages/MessageHeader.jsx @@ -45,7 +45,7 @@ const MessageHeader = ({ isSearchView = false }) => { <>
(endpoint === 'chatGPTBrowser' ? null : setSaveAsDialogShow(true))} diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index 7eac1e1ff..770eaacd9 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -1,11 +1,10 @@ import { useEffect } from 'react'; import store from '~/store'; -import TrashIcon from '../svg/TrashIcon'; -import { Dialog, DialogTrigger } from '../ui/Dialog.tsx'; +import { Dialog } from '../ui/Dialog.tsx'; import DialogTemplate from '../ui/DialogTemplate'; import { useClearConversationsMutation } from '~/data-provider'; -export default function ClearConvos() { +const ClearConvos = ({ open, onOpenChange}) => { const { newConversation } = store.useConversation(); const { refreshConversations } = store.useConversations(); const clearConvosMutation = useClearConversationsMutation(); @@ -23,13 +22,7 @@ export default function ClearConvos() { }, [clearConvosMutation.isSuccess]); return ( - - - - + ); -} +}; + +export default ClearConvos; diff --git a/client/src/components/Nav/DarkMode.jsx b/client/src/components/Nav/DarkMode.jsx index 753f56566..53577f659 100644 --- a/client/src/components/Nav/DarkMode.jsx +++ b/client/src/components/Nav/DarkMode.jsx @@ -1,9 +1,9 @@ -import React, { useState, useContext } from 'react'; +import { forwardRef, useContext } from 'react'; import DarkModeIcon from '../svg/DarkModeIcon'; import LightModeIcon from '../svg/LightModeIcon'; import { ThemeContext } from '~/hooks/ThemeContext'; -export default function DarkMode() { +const DarkMode = forwardRef(() => { const { theme, setTheme } = useContext(ThemeContext); const clickHandler = () => setTheme(theme === 'dark' ? 'light' : 'dark'); @@ -18,4 +18,6 @@ export default function DarkMode() { {mode} ); -} +}); + +export default DarkMode; diff --git a/client/src/components/Nav/ExportConversation/ExportModel.jsx b/client/src/components/Nav/ExportConversation/ExportModel.jsx index a91daeecd..1366ef29a 100644 --- a/client/src/components/Nav/ExportConversation/ExportModel.jsx +++ b/client/src/components/Nav/ExportConversation/ExportModel.jsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useRecoilValue, useRecoilCallback } from 'recoil'; import filenamify from 'filenamify'; import exportFromJSON from 'export-from-json'; import download from 'downloadjs'; import DialogTemplate from '~/components/ui/DialogTemplate.jsx'; -import { Dialog, DialogClose, DialogButton } from '~/components/ui/Dialog.tsx'; +import { Dialog, DialogButton } from '~/components/ui/Dialog.tsx'; import { Input } from '~/components/ui/Input.tsx'; import { Label } from '~/components/ui/Label.tsx'; import { Checkbox } from '~/components/ui/Checkbox.tsx'; diff --git a/client/src/components/Nav/ExportConversation/index.jsx b/client/src/components/Nav/ExportConversation/index.jsx index d5742a508..e883dce41 100644 --- a/client/src/components/Nav/ExportConversation/index.jsx +++ b/client/src/components/Nav/ExportConversation/index.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState, forwardRef } from 'react'; import { useRecoilValue } from 'recoil'; import { Download } from 'lucide-react'; import { cn } from '~/utils/'; @@ -7,7 +7,7 @@ import ExportModel from './ExportModel'; import store from '~/store'; -export default function ExportConversation() { +const ExportConversation = forwardRef(() => { const [open, setOpen] = useState(false); const conversation = useRecoilValue(store.conversation) || {}; @@ -37,4 +37,6 @@ export default function ExportConversation() { ); -} +}); + +export default ExportConversation; \ No newline at end of file diff --git a/client/src/components/Nav/Logout.jsx b/client/src/components/Nav/Logout.jsx index 035008dea..212a30bc3 100644 --- a/client/src/components/Nav/Logout.jsx +++ b/client/src/components/Nav/Logout.jsx @@ -1,8 +1,8 @@ -import React from 'react'; +import { forwardRef } from 'react'; import LogOutIcon from '../svg/LogOutIcon'; import { useAuthContext } from '~/hooks/AuthContext'; -export default function Logout() { +const Logout = forwardRef(() => { const { user, logout } = useAuthContext(); const handleLogout = () => { @@ -20,4 +20,6 @@ export default function Logout() { Log out ); -} +}); + +export default Logout; diff --git a/client/src/components/Nav/NavLink.jsx b/client/src/components/Nav/NavLink.jsx index bd677406a..85303d46e 100644 --- a/client/src/components/Nav/NavLink.jsx +++ b/client/src/components/Nav/NavLink.jsx @@ -1,20 +1,25 @@ -import React from 'react'; +import { forwardRef } from 'react'; +import { cn } from '~/utils/'; -export default function NavLink({ svg, text, clickHandler }) { - const props = { - className: - 'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10' - }; +const NavLink = forwardRef((props, ref) => { + const { svg, text, clickHandler, className = '' } = props; + const defaultProps = {}; + + defaultProps.className = cn( + 'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10', + className + ); if (clickHandler) { - props.onClick = clickHandler; - console.log('clickHandler: ', clickHandler); + defaultProps.onClick = clickHandler; } return ( - + {svg()} {text} ); -} +}); + +export default NavLink; diff --git a/client/src/components/Nav/NavLinks.jsx b/client/src/components/Nav/NavLinks.jsx index 96c46259d..b1bcf5084 100644 --- a/client/src/components/Nav/NavLinks.jsx +++ b/client/src/components/Nav/NavLinks.jsx @@ -1,70 +1,111 @@ import { Menu, Transition } from '@headlessui/react'; -import { Fragment, useEffect, useRef, useState } from 'react'; +import { Fragment, useState } from 'react'; +import { useRecoilValue } from 'recoil'; import SearchBar from './SearchBar'; +import TrashIcon from '../svg/TrashIcon'; +import { Download } from 'lucide-react'; +import NavLink from './NavLink'; +import ExportModel from './ExportConversation/ExportModel'; import ClearConvos from './ClearConvos'; import DarkMode from './DarkMode'; import Logout from './Logout'; -import ExportConversation from './ExportConversation'; import { useAuthContext } from '~/hooks/AuthContext'; import { cn } from '~/utils/'; import DotsIcon from '../svg/DotsIcon'; +import store from '~/store'; + export default function NavLinks({ clearSearch, isSearchEnabled }) { - const { user, logout } = useAuthContext(); + const [showExports, setShowExports] = useState(false); + const [showClearConvos, setShowClearConvos] = useState(false); + const { user } = useAuthContext(); + + const conversation = useRecoilValue(store.conversation) || {}; + + const exportable = + conversation?.conversationId && + conversation?.conversationId !== 'new' && + conversation?.conversationId !== 'search'; + + const clickHandler = () => { + if (exportable) setShowExports(true); + }; + return ( - - {({ open }) => ( - <> - -
-
- + <> + + {({ open }) => ( + <> + +
+
+ +
-
-
- {user?.name || 'USER'} -
- - +
+ {user?.name || 'USER'} +
+ + - - - - {({}) => <>{!!isSearchEnabled && }} - - {({}) => } - -
- {({}) => } - {({}) => } - -
- - - -
-
- - )} -
+ + + + {!!isSearchEnabled && } + + + } + text="Export conversation" + clickHandler={clickHandler} + /> + +
+ + + + + } + text="Clear conversations" + clickHandler={() => setShowClearConvos(true)} + /> + +
+ + + + + + + )} + + {showExports && } + {showClearConvos && } + ); } diff --git a/client/src/components/Nav/SearchBar.jsx b/client/src/components/Nav/SearchBar.jsx index afe9032d5..e982b92c8 100644 --- a/client/src/components/Nav/SearchBar.jsx +++ b/client/src/components/Nav/SearchBar.jsx @@ -1,8 +1,10 @@ +import { forwardRef } from 'react'; import { Search } from 'lucide-react'; import { useRecoilState } from 'recoil'; import store from '~/store'; -export default function SearchBar({ clearSearch }) { +const SearchBar = forwardRef((props, ref) => { + const { clearSearch } = props; const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery); const handleKeyUp = (e) => { @@ -19,7 +21,10 @@ export default function SearchBar({ clearSearch }) { }; return ( -
+
{}
); -} +}); + +export default SearchBar; diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index b3d1907f9..ac3ff9dc7 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef, useContext } from 'react'; import NewChat from './NewChat'; +import Panel from '../svg/Panel'; import Spinner from '../svg/Spinner'; import Pages from '../Conversations/Pages'; import Conversations from '../Conversations'; @@ -198,62 +199,21 @@ export default function Nav({ navVisible, setNavVisible }) {
{!navVisible && ( )} diff --git a/client/src/components/svg/Panel.jsx b/client/src/components/svg/Panel.jsx new file mode 100644 index 000000000..c82a2ec46 --- /dev/null +++ b/client/src/components/svg/Panel.jsx @@ -0,0 +1,47 @@ +import { cn } from '~/utils/'; + +export default function Panel({ open = false, className }) { + const openPanel = ( + + + + + + ); + + const closePanel = ( + + + + + + ); + + if (open) { + return openPanel; + } else { + return closePanel; + } +} diff --git a/client/src/components/ui/DialogTemplate.jsx b/client/src/components/ui/DialogTemplate.jsx index 5d4d1eb24..0dc932ef4 100644 --- a/client/src/components/ui/DialogTemplate.jsx +++ b/client/src/components/ui/DialogTemplate.jsx @@ -1,5 +1,4 @@ -import React from 'react'; - +import { forwardRef } from 'react'; import { DialogClose, DialogContent, @@ -10,21 +9,22 @@ import { } from './Dialog.tsx'; import { cn } from '~/utils/'; -export default function DialogTemplate({ - title, - description, - main, - buttons, - leftButtons, - selection, - className -}) { +const DialogTemplate = forwardRef((props, ref) => { + const { + title, + description, + main, + buttons, + leftButtons, + selection, + className + } = props; const { selectHandler, selectClasses, selectText } = selection || {}; const defaultSelect = 'bg-gray-900 text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900'; return ( - + {title} @@ -51,4 +51,6 @@ export default function DialogTemplate({ ); -} +}); + +export default DialogTemplate; diff --git a/client/src/utils/getIcon.jsx b/client/src/utils/getIcon.jsx index d84fdc07f..c1c9c4ff4 100644 --- a/client/src/utils/getIcon.jsx +++ b/client/src/utils/getIcon.jsx @@ -1,6 +1,3 @@ -import { clsx } from 'clsx'; -import React from 'react'; -import { twMerge } from 'tailwind-merge'; import GPTIcon from '../components/svg/GPTIcon'; import BingIcon from '../components/svg/BingIcon'; import { useAuthContext } from '~/hooks/AuthContext'; @@ -8,12 +5,13 @@ import { useAuthContext } from '~/hooks/AuthContext'; const getIcon = (props) => { // { size = 30, isCreatedByUser, model, chatGptLabel, error, ...props } const { size = 30, isCreatedByUser, button, model } = props; - const { user, logout } = useAuthContext(); + // eslint-disable-next-line react-hooks/rules-of-hooks + const { user } = useAuthContext(); if (isCreatedByUser) return (
{ className="rounded-sm" src={ user?.avatar || - `https://api.dicebear.com/6.x/initials/svg?seed=${user?.name}&fontFamily=Verdana&fontSize=36` + `https://api.dicebear.com/6.x/initials/svg?seed=${user?.name || 'User'}&fontFamily=Verdana&fontSize=36` } alt="avatar" /> diff --git a/package-lock.json b/package-lock.json index 82a469230..0f27d4160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,7 +114,7 @@ "filenamify": "^6.0.0", "html2canvas": "^1.4.1", "lodash": "^4.17.21", - "lucide-react": "^0.113.0", + "lucide-react": "^0.220.0", "pino": "^8.12.1", "rc-input-number": "^7.4.2", "react": "^18.2.0", @@ -12104,8 +12104,9 @@ } }, "node_modules/lucide-react": { - "version": "0.113.0", - "license": "ISC", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.220.0.tgz", + "integrity": "sha512-bYtGUsLAWBvZu+BzAU/ziP1gzE4LwMEXLnlgSr1yUKEPPalLG77JLd5GdYebOVkpm+GtqRqnp6tEKDX7Bm8ZlQ==", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } @@ -22692,7 +22693,7 @@ "filenamify": "^6.0.0", "html2canvas": "^1.4.1", "lodash": "^4.17.21", - "lucide-react": "^0.113.0", + "lucide-react": "0.220.0", "path": "^0.12.7", "pino": "^8.12.1", "postcss": "^8.4.21", @@ -26275,7 +26276,9 @@ } }, "lucide-react": { - "version": "0.113.0", + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.220.0.tgz", + "integrity": "sha512-bYtGUsLAWBvZu+BzAU/ziP1gzE4LwMEXLnlgSr1yUKEPPalLG77JLd5GdYebOVkpm+GtqRqnp6tEKDX7Bm8ZlQ==", "requires": {} }, "magic-string": {