From 99201318b57d90d08a87f07e4d40aaca4d691a2d Mon Sep 17 00:00:00 2001 From: subnub Date: Thu, 27 Jun 2024 02:10:03 -0400 Subject: [PATCH] lots more changes --- package.json | 2 + src/api/filesAPI.ts | 12 ++ src/axiosInterceptor/index.js | 3 - src/components/Dataform/index.jsx | 1 + src/components/Header/Header.jsx | 80 ---------- src/components/Header/index.jsx | 213 ------------------------- src/components/Header/index.tsx | 35 ++++ src/components/Homepage/HomePage.jsx | 2 +- src/components/SearchBar/index.tsx | 131 +++++++++++++++ src/components/SearchBarItem/index.tsx | 84 ++++++++++ src/components/SettingsPage/index.jsx | 2 +- src/hooks/files.ts | 25 ++- src/hooks/folders.ts | 2 +- src/hooks/utils.ts | 4 +- src/routers/AppRouter.jsx | 2 +- src/styles/components/_Spinner.scss | 73 +++++---- src/types/folders.ts | 1 + src/utils/cancelTokenManager.ts | 1 + 18 files changed, 339 insertions(+), 334 deletions(-) delete mode 100644 src/components/Header/Header.jsx delete mode 100644 src/components/Header/index.jsx create mode 100644 src/components/Header/index.tsx create mode 100644 src/components/SearchBar/index.tsx create mode 100644 src/components/SearchBarItem/index.tsx diff --git a/package.json b/package.json index bfbd6c5..b7d83f2 100755 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "helmet": "^3.21.2", "history": "^4.10.1", "jsonwebtoken": "^9.0.2", + "lodash.debounce": "^4.0.8", "moment": "^2.24.0", "mongodb": "^6.7.0", "mongoose": "^8.4.1", @@ -116,6 +117,7 @@ "vite": "^5.2.13" }, "devDependencies": { + "@types/lodash": "^4.17.5", "@typescript-eslint/eslint-plugin": "^2.20.0", "@typescript-eslint/parser": "^2.20.0", "concurrently": "^8.2.2", diff --git a/src/api/filesAPI.ts b/src/api/filesAPI.ts index 968055f..139dd67 100644 --- a/src/api/filesAPI.ts +++ b/src/api/filesAPI.ts @@ -80,6 +80,18 @@ export const getFileThumbnailAPI = async (thumbnailID: string) => { return imgUrl; }; +export const getSuggestedListAPI = async ({ + queryKey, +}: QueryFunctionContext<[string, { searchText: string }]>) => { + const [_key, { searchText }] = queryKey; + const response = await axios.get(`/file-service/suggested-list`, { + params: { + search: searchText, + }, + }); + return response.data; +}; + // PATCH export const renameFileAPI = async (fileID: string, name: string) => { const response = await axios.patch(`/file-service/rename`, { diff --git a/src/axiosInterceptor/index.js b/src/axiosInterceptor/index.js index b942033..1ad2452 100644 --- a/src/axiosInterceptor/index.js +++ b/src/axiosInterceptor/index.js @@ -38,12 +38,9 @@ axiosRetry.interceptors.response.use( }, (error) => { return new Promise((resolve, reject) => { - //console.log("request interceptor failed", error.config.url); - let originalRequest = error.config; if (error.response.status !== 401) { - //console.log("error does not equal 401"); return reject(error); } diff --git a/src/components/Dataform/index.jsx b/src/components/Dataform/index.jsx index 199408f..a91da1f 100644 --- a/src/components/Dataform/index.jsx +++ b/src/components/Dataform/index.jsx @@ -5,6 +5,7 @@ import { useInfiniteScroll } from "../../hooks/infiniteScroll"; import Files from "../Files"; import { memo, useEffect, useState } from "react"; import SpinnerPage from "../SpinnerPage"; +import SearchBar from "../SearchBar"; const DataForm = memo(() => { const { diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx deleted file mode 100644 index 0782bc0..0000000 --- a/src/components/Header/Header.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react"; -import { useNavigate } from "react-router-dom"; - -const Header = (props) => { - const navigate = useNavigate(); - - return ( -
-
-
-
- - -
-
- -
-
-
-
- ); -}; - -export default Header; diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx deleted file mode 100644 index 0f4f213..0000000 --- a/src/components/Header/index.jsx +++ /dev/null @@ -1,213 +0,0 @@ -import Header from "./Header"; -import { startLogout } from "../../actions/auth"; -import { showSettings } from "../../actions/settings"; -import { setSearch } from "../../actions/filter"; -import { loadMoreItems } from "../../actions/main"; -import { setParent, resetParentList } from "../../actions/parent"; -import { startSetFiles } from "../../actions/files"; -import { startSetFolders } from "../../actions/folders"; -import env from "../../enviroment/envFrontEnd"; -import axios from "../../axiosInterceptor"; -import { connect } from "react-redux"; -import React from "react"; -import withNavigate from "../HocComponent"; - -const currentURL = env.url; - -class HeaderContainer extends React.Component { - constructor(props) { - super(props); - - this.searchValue = ""; - - this.state = { - focused: false, - suggestedList: { - fileList: [], - folderList: [], - }, - }; - } - - searchEvent = (e) => { - e.preventDefault(); - - const value = this.props.search; - - // console.log("Search value". value) - - const parent = "/"; - this.props.dispatch(setParent(parent)); - this.props.dispatch(loadMoreItems(true)); - this.props.dispatch(startSetFiles(undefined, undefined, value)); - this.props.dispatch(startSetFolders(undefined, undefined, value)); - this.props.dispatch(resetParentList()); - }; - - searchOnChange = (e) => { - const value = e.target.value; - this.searchValue = value; - - this.props.dispatch(setSearch(value)); - this.searchSuggested(); - }; - - showSuggested = () => { - this.setState(() => { - return { - ...this.state, - focused: true, - }; - }); - }; - - hideSuggested = () => { - this.setState(() => { - return { - ...this.state, - focused: false, - }; - }); - }; - - selectSuggested = () => { - this.props.navigate(`/search/${this.searchValue}`); - - this.searchValue = ""; - - this.setState(() => { - return { - ...this.state, - suggestedList: { - fileList: [], - folderList: [], - }, - }; - }); - }; - - searchSuggested = () => { - return; - - // if (this.searchValue === "") { - - // return this.setState(() => { - // return { - // ...this.state, - // suggestedList: { - // fileList: [], - // folderList: [] - // } - // } - // }) - // } - - // const url = !env.googleDriveEnabled ? currentURL +`/file-service/suggested-list?search=${this.searchValue}` : currentURL +`/file-service-google-mongo/suggested-list?search=${this.searchValue}` - - // axios.get(url).then((results) => { - - // this.setState(() => { - // return { - // ...this.state, - // suggestedList: results.data - // } - // }) - - // }).catch((err) => { - // console.log(err) - // }) - }; - - showSettings = () => { - this.props.dispatch(showSettings()); - }; - - logoutUser = () => { - this.props.dispatch(startLogout()); - }; - - itemClick = () => { - console.log("item click"); - }; - - selectSuggestedByParent = () => { - // const parent = this.props.parent === "/" ? "home" : this.props.parent; - - const parent = this.props.parent; - - this.props.navigate( - `/search/${this.searchValue}?parent=${parent}&folder_search=true` - ); - - this.searchValue = ""; - - this.setState(() => { - return { - ...this.state, - suggestedList: { - fileList: [], - folderList: [], - }, - }; - }); - }; - - selectSuggestedByStorageType = () => { - this.props.navigate(`/search/${this.searchValue}?storageType=stripe`); - - this.searchValue = ""; - - this.setState(() => { - return { - ...this.state, - suggestedList: { - fileList: [], - folderList: [], - }, - }; - }); - }; - - goToSettings = () => { - this.props.navigate("/settings"); - }; - - getProfilePic = () => { - if (env.name && env.name.length !== 0) { - return env.name.substring(0, 1).toUpperCase(); - } else if (env.emailAddress && env.emailAddress.length !== 0) { - return env.emailAddress.substring(0, 1).toUpperCase(); - } else { - return "?"; - } - }; - - render() { - return ( -
- ); - } -} - -const connectPropToStore = (state) => ({ - search: state.filter.search, - parentNameList: state.parent.parentNameList, - parent: state.parent.parent, -}); - -export default connect(connectPropToStore)(withNavigate(HeaderContainer)); diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx new file mode 100644 index 0000000..b5627a2 --- /dev/null +++ b/src/components/Header/index.tsx @@ -0,0 +1,35 @@ +import { useNavigate } from "react-router-dom"; +import SearchBar from "../SearchBar"; + +const Header = () => { + const navigate = useNavigate(); + return ( +
+
+
+ navigate("/")} + > + logo + +
+ + +
+
+ ); +}; + +export default Header; diff --git a/src/components/Homepage/HomePage.jsx b/src/components/Homepage/HomePage.jsx index 9f5d92c..11aa675 100644 --- a/src/components/Homepage/HomePage.jsx +++ b/src/components/Homepage/HomePage.jsx @@ -1,4 +1,4 @@ -import Header from "../Header"; +import Header from "../Header/"; import LeftSection from "../LeftSection"; import MainSection from "../MainSection"; import Uploader from "../Uploader"; diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx new file mode 100644 index 0000000..42a0577 --- /dev/null +++ b/src/components/SearchBar/index.tsx @@ -0,0 +1,131 @@ +import { memo, useCallback, useEffect, useMemo, useState } from "react"; +import { useAppDispatch } from "../../hooks/store"; +import { useSearchSuggestions } from "../../hooks/files"; +import debounce from "lodash/debounce"; +import { useNavigate } from "react-router-dom"; +import { useClickOutOfBounds } from "../../hooks/utils"; +import SearchBarItem from "../SearchBarItem"; +import { FolderInterface } from "../../types/folders"; +import { FileInterface } from "../../types/file"; +import { setPopupFile } from "../../actions/popupFile"; +import Spinner from "../Spinner"; +import classNames from "classnames"; + +const SearchBar = memo(() => { + const [searchText, setSearchText] = useState(""); + const dispatch = useAppDispatch(); + const [debouncedSearchText, setDebouncedSearchText] = useState(""); + const { data: searchSuggestions, isLoading: isLoadingSearchSuggestions } = + useSearchSuggestions(debouncedSearchText); + const navigate = useNavigate(); + + const debouncedSetSearchText = useMemo( + () => debounce(setDebouncedSearchText, 500), + [] + ); + + useEffect(() => { + debouncedSetSearchText(searchText); + return () => { + debouncedSetSearchText.cancel(); + }; + }, [searchText, debouncedSetSearchText]); + + const resetState = useCallback(() => { + setSearchText(""); + setDebouncedSearchText(""); + }, []); + + const { wrapperRef } = useClickOutOfBounds(resetState); + + // TODO: Fix any + const onSearch = (e: any) => { + e.preventDefault(); + setSearchText(""); + setDebouncedSearchText(""); + if (searchText.length) { + navigate(`/search/${searchText}`); + } else { + navigate("/home"); + } + }; + + const onChangeSearch = (e: any) => { + setSearchText(e.target.value); + }; + + const fileClick = (file: FileInterface) => { + dispatch(setPopupFile({ showPopup: true, ...file })); + resetState(); + }; + + const folderClick = (folder: FolderInterface) => { + navigate(`/folder/${folder?._id}`); + resetState(); + }; + + return ( +
+ + {isLoadingSearchSuggestions ? ( +
+ ) : ( + search + )} +
+ +
+ {searchSuggestions?.folderList.length === 0 && + searchSuggestions?.fileList.length === 0 ? ( +
+ No Results +
+ ) : undefined} + {searchSuggestions?.folderList.map((folder: FolderInterface) => ( + + ))} + {searchSuggestions?.fileList.map((file: FileInterface) => ( + + ))} +
+
+ ); +}); + +export default SearchBar; diff --git a/src/components/SearchBarItem/index.tsx b/src/components/SearchBarItem/index.tsx new file mode 100644 index 0000000..db29d57 --- /dev/null +++ b/src/components/SearchBarItem/index.tsx @@ -0,0 +1,84 @@ +import { FileInterface } from "../../types/file"; +import { FolderInterface } from "../../types/folders"; +import { useMemo } from "react"; +import { getFileColor, getFileExtension } from "../../utils/files"; + +interface SearchBarItemProps { + file?: FileInterface; + folder?: FolderInterface; + type: "file" | "folder"; + fileClick: (file: FileInterface) => void; + folderClick: (folder: FolderInterface) => void; +} + +const SearchBarItem = (props: SearchBarItemProps) => { + const { type, folder, file, fileClick, folderClick } = props; + const fileExtension = useMemo( + () => getFileExtension(file?.filename || "", 3), + [file?.filename] + ); + + const imageColor = useMemo( + () => getFileColor(file?.filename || ""), + [file?.filename] + ); + + if (type === "folder" && folder) { + return ( +
folderClick(folder)} + > +
+ +
+ + {folder.name} + +
+ ); + } else if (type === "file" && file) { + return ( +
fileClick(file)} + > +
+ +
+ + {fileExtension} + +
+
+
+ + {file.filename} + +
+ ); + } + return
; +}; + +export default SearchBarItem; diff --git a/src/components/SettingsPage/index.jsx b/src/components/SettingsPage/index.jsx index 9cbed12..1549186 100644 --- a/src/components/SettingsPage/index.jsx +++ b/src/components/SettingsPage/index.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Header from "../Header/index"; +import Header from "../Header"; import axios from "../../axiosInterceptor"; import env from "../../enviroment/envFrontEnd"; import Swal from "sweetalert2"; diff --git a/src/hooks/files.ts b/src/hooks/files.ts index d936f48..ea04239 100644 --- a/src/hooks/files.ts +++ b/src/hooks/files.ts @@ -1,9 +1,15 @@ -import { useInfiniteQuery, useQuery, useQueryClient } from "react-query"; +import { + QueryFunctionContext, + useInfiniteQuery, + useQuery, + useQueryClient, +} from "react-query"; import { useParams } from "react-router-dom"; import { getFileThumbnailAPI, getFilesListAPI, getQuickFilesListAPI, + getSuggestedListAPI, } from "../api/filesAPI"; import { useCallback, useEffect, useState } from "react"; import { useSelector } from "react-redux"; @@ -17,7 +23,7 @@ export const useFiles = () => { "files", { parent: params.id || "/", - search: "", + search: params.query || "", sortBy, limit: undefined, }, @@ -122,3 +128,18 @@ export const useThumbnail = (hasThumbnail: boolean, thumbnailID?: string) => { return { ...state, imageOnError }; }; + +export const useSearchSuggestions = (searchText: string) => { + const searchQuery = useQuery( + [ + "search", + { + searchText, + }, + ], + getSuggestedListAPI, + { enabled: searchText.length !== 0 } + ); + + return { ...searchQuery }; +}; diff --git a/src/hooks/folders.ts b/src/hooks/folders.ts index c1d6c5e..1536d22 100644 --- a/src/hooks/folders.ts +++ b/src/hooks/folders.ts @@ -11,7 +11,7 @@ export const useFolders = () => { "folders", { parent: params.id || "/", - search: "", + search: params.query || "", sortBy, limit: undefined, }, diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index f9be222..3e7b9c9 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -5,8 +5,8 @@ export const useUtils = () => { const params = useParams(); const isHome = useMemo(() => { - return !params.id; - }, [params.id]); + return !params.id && !params.query; + }, [params.id, params.query]); return { isHome }; }; diff --git a/src/routers/AppRouter.jsx b/src/routers/AppRouter.jsx index 8fbf456..e5d8f4d 100755 --- a/src/routers/AppRouter.jsx +++ b/src/routers/AppRouter.jsx @@ -50,7 +50,7 @@ const AppRouter = () => { /> diff --git a/src/styles/components/_Spinner.scss b/src/styles/components/_Spinner.scss index 420059b..7813a2b 100644 --- a/src/styles/components/_Spinner.scss +++ b/src/styles/components/_Spinner.scss @@ -1,39 +1,52 @@ .spinner { - border: 3px solid #f3f3f3; - border-top: 3px solid #3498db; - border-radius: 50%; - width: 40px; - height: 40px; - margin-left: 50%; - text-align: center; - animation: spin 2s linear infinite; + border: 3px solid #f3f3f3; + border-top: 3px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + margin-left: 50%; + text-align: center; + animation: spin 2s linear infinite; +} +.spinner-small { + border: 3px solid #f3f3f3; + border-top: 3px solid #3498db; + border-radius: 50%; + width: 20px; + height: 20px; + margin-left: 50%; + text-align: center; + animation: spin 2s linear infinite; +} +@keyframes spin { + 0% { + transform: rotate(0deg); } - - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } + 100% { + transform: rotate(360deg); } +} - .spinner--small-margin { - margin-left: 4px; - } +.spinner--small-margin { + margin-left: 4px; +} - .spinner__no-margin { - margin: 0; - margin-bottom: 108px; +.spinner__no-margin { + margin: 0; + margin-bottom: 108px; - // @media (max-width: $desktop-breakpoint) { - // margin-bottom: 108px; - // } - } + // @media (max-width: $desktop-breakpoint) { + // margin-bottom: 108px; + // } +} - .homepage__spinner__wrapper { - position: fixed; - height: 100vh; - width: 100vw; - display: flex; - justify-content: center; - align-items: center; - } \ No newline at end of file +.homepage__spinner__wrapper { + position: fixed; + height: 100vh; + width: 100vw; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/types/folders.ts b/src/types/folders.ts index 6046b2b..7099cb3 100644 --- a/src/types/folders.ts +++ b/src/types/folders.ts @@ -1,4 +1,5 @@ export interface FolderInterface { + _id: string; name: string; parent: string; owner: string; diff --git a/src/utils/cancelTokenManager.ts b/src/utils/cancelTokenManager.ts index 3c72821..ec381f1 100644 --- a/src/utils/cancelTokenManager.ts +++ b/src/utils/cancelTokenManager.ts @@ -22,6 +22,7 @@ export const getCancelToken = (id: string) => { export const cancelAllFileUploads = () => { for (const key in cancelTokens) { cancelTokens[key].cancel(); + delete cancelTokens[key]; } };