diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index 242efb1a..15c9fcb9 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -33,7 +33,6 @@ import { selectConversations, selectModalStateDeleteConv, selectSelectedDocs, - selectSelectedDocsStatus, selectSourceDocs, selectPaginatedDocuments, setConversations, @@ -86,10 +85,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { const [apiKeyModalState, setApiKeyModalState] = useState('INACTIVE'); - const isSelectedDocsSet = useSelector(selectSelectedDocsStatus); - const [selectedDocsModalState, setSelectedDocsModalState] = - useState(isSelectedDocsSet ? 'INACTIVE' : 'ACTIVE'); - const [uploadModalState, setUploadModalState] = useState('INACTIVE'); @@ -493,11 +488,13 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { setModalState={setModalStateDeleteConv} handleDeleteAllConv={handleDeleteAllConversations} /> - + {uploadModalState === 'ACTIVE' && ( + setUploadModalState('INACTIVE')} + > + )} ); } diff --git a/frontend/src/modals/ConfirmationModal.tsx b/frontend/src/modals/ConfirmationModal.tsx index 0b39440b..c69dcedd 100644 --- a/frontend/src/modals/ConfirmationModal.tsx +++ b/frontend/src/modals/ConfirmationModal.tsx @@ -1,6 +1,6 @@ -import Exit from '../assets/exit.svg'; import { ActiveState } from '../models/misc'; import { useTranslation } from 'react-i18next'; +import WrapperModal from './WrapperModal'; function ConfirmationModal({ message, modalState, @@ -20,49 +20,43 @@ function ConfirmationModal({ }) { const { t } = useTranslation(); return ( -
-
-
- -
-

- {message} -

-
-
- - + <> + {modalState === 'ACTIVE' && ( + { + setModalState('INACTIVE'); + handleCancel && handleCancel(); + }} + > +
+
+

+ {message} +

+
+
+ + +
-
-
-
+ + )} + ); } diff --git a/frontend/src/modals/CreateAPIKeyModal.tsx b/frontend/src/modals/CreateAPIKeyModal.tsx index eb085a28..5c8c75b8 100644 --- a/frontend/src/modals/CreateAPIKeyModal.tsx +++ b/frontend/src/modals/CreateAPIKeyModal.tsx @@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import userService from '../api/services/userService'; -import Exit from '../assets/exit.svg'; import Dropdown from '../components/Dropdown'; import Input from '../components/Input'; import { CreateAPIKeyModalProps, Doc } from '../models/misc'; import { selectSourceDocs } from '../preferences/preferenceSlice'; +import WrapperModal from './WrapperModal'; const embeddingsName = import.meta.env.VITE_EMBEDDINGS_NAME || @@ -73,91 +73,82 @@ export default function CreateAPIKeyModal({ handleFetchPrompts(); }, []); return ( -
-
- -
- - {t('modals.createAPIKey.label')} - -
-
- - {t('modals.createAPIKey.apiKeyName')} - - setAPIKeyName(e.target.value)} - > -
-
- { - setSourcePath(selection); - }} - options={extractDocPaths()} - size="w-full" - rounded="xl" - border="border" - /> -
-
- - setPrompt(value) - } - size="w-full" - border="border" - /> -
-
-

- {t('modals.createAPIKey.chunks')} -

- setChunk(value)} - size="w-full" - border="border" - /> -
- + +
+ + {t('modals.createAPIKey.label')} +
-
+
+ + {t('modals.createAPIKey.apiKeyName')} + + setAPIKeyName(e.target.value)} + > +
+
+ { + setSourcePath(selection); + }} + options={extractDocPaths()} + size="w-full" + rounded="xl" + border="border" + /> +
+
+ + setPrompt(value) + } + size="w-full" + border="border" + /> +
+
+

+ {t('modals.createAPIKey.chunks')} +

+ setChunk(value)} + size="w-full" + border="border" + /> +
+ + ); } diff --git a/frontend/src/modals/ShareConversationModal.tsx b/frontend/src/modals/ShareConversationModal.tsx index fbb49468..3f87839e 100644 --- a/frontend/src/modals/ShareConversationModal.tsx +++ b/frontend/src/modals/ShareConversationModal.tsx @@ -10,7 +10,6 @@ import { import Dropdown from '../components/Dropdown'; import { Doc } from '../models/misc'; import Spinner from '../assets/spinner.svg'; -import Exit from '../assets/exit.svg'; const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; const embeddingsName = import.meta.env.VITE_EMBEDDINGS_NAME || @@ -19,6 +18,7 @@ const embeddingsName = type StatusType = 'loading' | 'idle' | 'fetched' | 'failed'; import conversationService from '../api/services/conversationService'; +import WrapperModal from './WrapperModal'; export const ShareConversationModal = ({ close, @@ -99,85 +99,78 @@ export const ShareConversationModal = ({ }; return ( -
-
- -
-

{t('modals.shareConv.label')}

-

{t('modals.shareConv.note')}

-
- {t('modals.shareConv.option')} - -
- {allowPrompt && ( -
- - setSourcePath(selection) - } - options={extractDocPaths(sourceDocs ?? [])} - size="w-full" - rounded="xl" + +
+

{t('modals.shareConv.label')}

+

{t('modals.shareConv.note')}

+
+ {t('modals.shareConv.option')} +
-
+
); }; diff --git a/frontend/src/modals/WrapperModal.tsx b/frontend/src/modals/WrapperModal.tsx new file mode 100644 index 00000000..f17e0a8a --- /dev/null +++ b/frontend/src/modals/WrapperModal.tsx @@ -0,0 +1,55 @@ +import React, { useEffect, useRef } from 'react'; +import { WrapperModalProps } from './types'; +import Exit from '../assets/exit.svg'; + +const WrapperModal: React.FC = ({ + children, + close, + isPerformingTask, +}) => { + const modalRef = useRef(null); + + useEffect(() => { + if (isPerformingTask) return; + const handleClickOutside = (event: MouseEvent) => { + if ( + modalRef.current && + !modalRef.current.contains(event.target as Node) + ) { + close(); + } + }; + + const handleEscapePress = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + close(); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleEscapePress); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleEscapePress); + }; + }, [close]); + + return ( +
+
+ {!isPerformingTask && ( + + )} + {children} +
+
+ ); +}; + +export default WrapperModal; diff --git a/frontend/src/modals/types/index.ts b/frontend/src/modals/types/index.ts new file mode 100644 index 00000000..976bf0e9 --- /dev/null +++ b/frontend/src/modals/types/index.ts @@ -0,0 +1,5 @@ +export type WrapperModalProps = { + children?: React.ReactNode; + isPerformingTask?: boolean; + close: () => void; +}; diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index f91a3355..db2e5855 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -255,9 +255,9 @@ const Documents: React.FC = ({
{/* Your Upload component */} setModalState('INACTIVE')} />
diff --git a/frontend/src/upload/Upload.tsx b/frontend/src/upload/Upload.tsx index 33a77ace..3299f808 100644 --- a/frontend/src/upload/Upload.tsx +++ b/frontend/src/upload/Upload.tsx @@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import userService from '../api/services/userService'; -import Exit from '../assets/exit.svg'; import ArrowLeft from '../assets/arrow-left.svg'; import FileUpload from '../assets/file_upload.svg'; import WebsiteCollect from '../assets/website_collect.svg'; @@ -17,15 +16,16 @@ import { setSourceDocs, selectSourceDocs, } from '../preferences/preferenceSlice'; +import WrapperModal from '../modals/WrapperModal'; function Upload({ - modalState, setModalState, isOnboarding, + close, }: { - modalState: ActiveState; setModalState: (state: ActiveState) => void; isOnboarding: boolean; + close: () => void; }) { const [docName, setDocName] = useState(''); const [urlName, setUrlName] = useState(''); @@ -603,7 +603,30 @@ function Upload({ ) : ( @@ -629,28 +652,18 @@ function Upload({ } return ( -
{ + close(); + setDocName(''); + setfiles([]); + setModalState('INACTIVE'); + setActiveTab(null); + }} > -
- {!isOnboarding && !progress && ( - - )} - {view} -
-
+ {view} + ); }