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 (
+
+ );
+};
+
+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 (
+
+ );
+});
+
+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];
}
};