├── .env.sample ├── .gitignore ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── src ├── API │ └── firebase.js ├── App.css ├── App.jsx ├── components │ ├── 404Page │ │ └── index.jsx │ ├── CreateFile │ │ └── index.jsx │ ├── CreateFolder │ │ └── index.jsx │ ├── Dashboard │ │ ├── BreadCrum.js │ │ │ └── index.jsx │ │ ├── FileComponent │ │ │ ├── FileComponent.css │ │ │ ├── Header.jsx │ │ │ └── index.jsx │ │ ├── FolderAdminComponent │ │ │ └── index.jsx │ │ ├── FolderComponent │ │ │ └── index.jsx │ │ ├── Home │ │ │ └── index.jsx │ │ ├── NavDashboard │ │ │ └── index.jsx │ │ ├── SubNav.js │ │ │ └── index.jsx │ │ └── index.jsx │ ├── DeleteButton │ │ └── index.jsx │ ├── Home │ │ └── index.jsx │ ├── Navbar │ │ └── index.jsx │ ├── UploadFile │ │ └── index.jsx │ └── authentication │ │ ├── Login │ │ └── index.jsx │ │ └── Register │ │ └── index.jsx ├── index.css ├── index.jsx ├── models │ ├── docs.js │ ├── files.js │ └── users.js └── redux │ ├── actionCreators │ ├── authActionCreators.js │ └── filefoldersActionCreators.js │ ├── actions │ ├── authActions.js │ └── filefoldersActions.js │ └── reducers │ ├── authReducer.js │ └── filefolderReducer.js └── vite.config.js /.env.sample: -------------------------------------------------------------------------------- 1 | VITE_APP_apiKey = your_data 2 | VITE_APP_authDomain = your_data 3 | VITE_APP_projectId = your_data 4 | VITE_APP_storageBucket = your_data 5 | VITE_APP_messagingSenderId = your_data 6 | VITE_APP_appId = your_data -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Js Firebase FIle Management System [V1.0] 2 | 3 | This is the file management system made with reactjs and frebase. where user cn upload files, create folders and sub folders, and also even user can create file and he/she can edit and save created file. 4 | 5 | **This is the upgraded version from CRA to VITE, React 18 and Firebase 10 (Tutorial of upgradation coming soon)** 6 | 7 | Demo Link Vercel 8 | 9 | ## Prerequities 10 | 1. Create Firsebase project , enable storage, Auth and Firebase Database 11 | 2. Create Web app and get credentials 12 | 3. Follow installation guide 13 | 14 | **note:** if you are getting any cors error from firebase then follow this link https://firebase.google.com/docs/storage/web/download-files#cors_configuration 15 | 16 | 17 | ## Installation Guide 18 | 19 | 1. Download repo and unzip it. 20 | 2. Open terminal under this repo and run `pnpm install` to install all dependencies 21 | 3. Now, create .env file in the root directory and copy the data from .env.sample file 22 | 4. Now, Create your firebase project on firebase console and fill your credentials in .env file 23 | 5. Now, run `pnpm start` to run development server. 24 | 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | File Management System 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-management-system-reactjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^1.2.35", 7 | "@fortawesome/free-solid-svg-icons": "^5.15.3", 8 | "@fortawesome/react-fontawesome": "^0.1.14", 9 | "@testing-library/jest-dom": "^5.12.0", 10 | "@testing-library/react": "^11.2.6", 11 | "@testing-library/user-event": "^12.8.3", 12 | "@vitejs/plugin-react": "^4.2.1", 13 | "@vitejs/plugin-react-swc": "^3.6.0", 14 | "bootstrap": "^5.0.1", 15 | "codemirror": "^5.61.1", 16 | "firebase": "^10.9.0", 17 | "react": "^18.2.0", 18 | "react-bootstrap": "^1.6.0", 19 | "react-codemirror2": "^7.2.1", 20 | "react-dom": "^18.2.0", 21 | "react-file-viewer": "^1.2.1", 22 | "react-redux": "^7.2.4", 23 | "react-router-dom": "^5.2.0", 24 | "react-toastify": "^7.0.4", 25 | "redux": "^4.1.0", 26 | "redux-devtools-extension": "^2.13.9", 27 | "redux-thunk": "^2.3.0", 28 | "vite": "^5.1.6", 29 | "web-vitals": "^1.1.1" 30 | }, 31 | "scripts": { 32 | "start": "vite", 33 | "build": "vite build", 34 | "preview": "vite preview" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/API/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/compat/app'; 2 | import 'firebase/compat/auth'; 3 | import 'firebase/compat/firestore'; 4 | import 'firebase/compat/storage'; 5 | 6 | const firebaseConfig = { 7 | apiKey: import.meta.env.VITE_APP_apiKey, 8 | authDomain: import.meta.env.VITE_APP_authDomain, 9 | projectId: import.meta.env.VITE_APP_projectId, 10 | storageBucket: import.meta.env.VITE_APP_storageBucket, 11 | messagingSenderId: import.meta.env.VITE_APP_messagingSenderId, 12 | appId: import.meta.env.VITE_APP_appId, 13 | }; 14 | 15 | firebase.initializeApp(firebaseConfig); 16 | 17 | const firestore = firebase.firestore(); 18 | export const database = { 19 | users: firestore.collection('users'), 20 | docs: firestore.collection('docs'), 21 | files: firestore.collection('files'), 22 | date: firebase.firestore.FieldValue.serverTimestamp(), 23 | }; 24 | 25 | export const storage = firebase.storage(); 26 | 27 | export const auth = firebase.auth(); 28 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | import { ToastContainer } from "react-toastify"; 4 | 5 | import Register from "./components/authentication/Register"; 6 | import Login from "./components/authentication/Login"; 7 | import NavbarComponent from "./components/Navbar"; 8 | import Dashboard from "./components/Dashboard"; 9 | 10 | import "./App.css"; 11 | 12 | import { useDispatch, useSelector } from "react-redux"; 13 | import { getUser } from "./redux/actionCreators/authActionCreators"; 14 | 15 | const App = () => { 16 | const dispatch = useDispatch(); 17 | const isLoggedIn = useSelector((state) => state.auth.isLoggedIn); 18 | 19 | useEffect(() => { 20 | if (!isLoggedIn) { 21 | dispatch(getUser()); 22 | } 23 | }, [dispatch]); 24 | return ( 25 |
26 | 27 | 28 | 29 | 30 | 31 |

Welcome to file management system

32 |
33 | }> 34 | }> 35 | 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /src/components/404Page/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Col, Container, Row } from "react-bootstrap"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const Page404 = () => { 6 | return ( 7 | 8 | 9 | 10 |

11 | 404 Page Not Found 12 |

13 | 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default Page404; 23 | -------------------------------------------------------------------------------- /src/components/CreateFile/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { faFileAlt, faTimes } from "@fortawesome/free-solid-svg-icons"; 3 | import { Button, Form, Modal } from "react-bootstrap"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { toast } from "react-toastify"; 6 | import { shallowEqual, useDispatch, useSelector } from "react-redux"; 7 | import { addFileUser } from "../../redux/actionCreators/filefoldersActionCreators"; 8 | 9 | const CreateFile = ({ currentFolder }) => { 10 | const [showModal, setShowModal] = useState(false); 11 | const [file, setFile] = useState(""); 12 | 13 | const dispatch = useDispatch(); 14 | const { userId, userFiles } = useSelector( 15 | (state) => ({ 16 | userId: state.auth.userId, 17 | userFiles: state.filefolders.userFiles, 18 | }), 19 | shallowEqual 20 | ); 21 | 22 | const handleFileSubmit = (e) => { 23 | e.preventDefault(); 24 | 25 | if (!file) return toast.dark("Please add file name!"); 26 | const fileExtension = 27 | file.split(".").length > 1 28 | ? file.split(".")[file.split(".").length - 1].toLowerCase() 29 | : "txt"; 30 | const allowedExtensions = [ 31 | "html", 32 | "php", 33 | "js", 34 | "jsx", 35 | "txt", 36 | "xml", 37 | "css", 38 | "c", 39 | "cpp", 40 | "java", 41 | "cs", 42 | "py", 43 | "json", 44 | ]; 45 | 46 | if (allowedExtensions.indexOf(fileExtension) === -1) { 47 | return toast.dark(`File with extension ${fileExtension} not allowed!`); 48 | } 49 | const fileName = 50 | file.split(".").length > 1 ? file : file + "." + fileExtension; 51 | 52 | const filteredFiles = 53 | currentFolder === "root folder" 54 | ? userFiles.filter( 55 | (file) => 56 | file.data.parent === "" && file.data.name === fileName.trim() 57 | ) 58 | : userFiles.filter( 59 | (file) => 60 | file.data.parent === currentFolder.docId && 61 | file.data.name === fileName.trim() 62 | ); 63 | 64 | if (filteredFiles.length > 0) 65 | return toast.dark("This is alredy present in folder"); 66 | 67 | if (currentFolder === "root folder") { 68 | dispatch( 69 | addFileUser({ 70 | uid: userId, 71 | parent: "", 72 | data: "", 73 | name: fileName, 74 | url: "", 75 | path: [], 76 | }) 77 | ); 78 | setFile(""); 79 | setShowModal(false); 80 | return; 81 | } 82 | 83 | const path = 84 | currentFolder.data.path.length > 0 85 | ? [ 86 | ...currentFolder.data.path, 87 | { id: currentFolder.docId, name: currentFolder.data.name }, 88 | ] 89 | : [{ id: currentFolder.docId, name: currentFolder.data.name }]; 90 | 91 | dispatch( 92 | addFileUser({ 93 | uid: userId, 94 | parent: currentFolder.docId, 95 | data: "", 96 | name: fileName, 97 | url: "", 98 | path: path, 99 | }) 100 | ); 101 | setFile(""); 102 | setShowModal(false); 103 | return; 104 | }; 105 | 106 | return ( 107 | <> 108 | setShowModal(false)}> 109 | 110 | Create File 111 | 118 | 119 | 120 |
121 | 122 | setFile(e.target.value)} 127 | /> 128 | 129 | 130 | 133 | 134 |
135 |
136 |
137 | 145 | 146 | ); 147 | }; 148 | 149 | export default CreateFile; 150 | -------------------------------------------------------------------------------- /src/components/CreateFolder/index.jsx: -------------------------------------------------------------------------------- 1 | import { faFolderPlus, faTimes } from "@fortawesome/free-solid-svg-icons"; 2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 3 | import React, { useState } from "react"; 4 | import { Button, Form, Modal } from "react-bootstrap"; 5 | import { shallowEqual, useDispatch, useSelector } from "react-redux"; 6 | import { toast } from "react-toastify"; 7 | import { addFolderUser } from "../../redux/actionCreators/filefoldersActionCreators"; 8 | 9 | const CreateFolder = ({ currentFolder }) => { 10 | const [showModal, setShowModal] = useState(false); 11 | const [folderName, setFolderName] = useState(""); 12 | 13 | const dispatch = useDispatch(); 14 | const { userId, userFolders } = useSelector( 15 | (state) => ({ 16 | userId: state.auth.userId, 17 | userFolders: state.filefolders.userFolders, 18 | }), 19 | shallowEqual 20 | ); 21 | 22 | const handleFolderSubmit = (e) => { 23 | e.preventDefault(); 24 | const filteredFolders = 25 | currentFolder === "root folder" 26 | ? userFolders.filter( 27 | (folder) => 28 | folder.data.parent === "" && 29 | folder.data.name === folderName.trim() 30 | ) 31 | : userFolders.filter( 32 | (folder) => 33 | folder.data.parent === currentFolder.docId && 34 | folder.data.name === folderName.trim() 35 | ); 36 | if (!folderName) return toast.dark("Please enter folder name!"); 37 | 38 | if (filteredFolders.length > 0) 39 | return toast.dark("This is alredy present in folder"); 40 | 41 | if (currentFolder === "root folder") { 42 | dispatch(addFolderUser(folderName, userId, "", [])); 43 | setFolderName(""); 44 | setShowModal(false); 45 | return; 46 | } 47 | 48 | const path = 49 | currentFolder.data.path.length > 0 50 | ? [ 51 | ...currentFolder.data.path, 52 | { id: currentFolder.docId, name: currentFolder.data.name }, 53 | ] 54 | : [{ id: currentFolder.docId, name: currentFolder.data.name }]; 55 | dispatch(addFolderUser(folderName, userId, currentFolder.docId, path)); 56 | setFolderName(""); 57 | setShowModal(false); 58 | return; 59 | }; 60 | return ( 61 | <> 62 | setShowModal(false)}> 63 | 64 | Create Folder 65 | 72 | 73 | 74 |
75 | 76 | setFolderName(e.target.value)} 81 | /> 82 | 83 | 84 | 87 | 88 |
89 |
90 |
91 | 99 | 100 | ); 101 | }; 102 | 103 | export default CreateFolder; 104 | -------------------------------------------------------------------------------- /src/components/Dashboard/BreadCrum.js/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Breadcrumb, Button } from "react-bootstrap"; 3 | import { useHistory } from "react-router-dom"; 4 | 5 | const BreadCrum = ({ currentFolder }) => { 6 | const history = useHistory(); 7 | return ( 8 | 9 | {currentFolder && currentFolder.data.path.length > 0 ? ( 10 | <> 11 | history.push("/dashboard")} 18 | className="text-decoration-none" 19 | > 20 | Root 21 | 22 | {currentFolder.data.path.map((folder, index) => ( 23 | 31 | history.push( 32 | currentFolder.data.createdBy === "admin" 33 | ? `/dashboard/folder/admin/${folder.id}` 34 | : `/dashboard/folder/${folder.id}` 35 | ) 36 | } 37 | > 38 | {folder.name} 39 | 40 | ))} 41 | 42 | {currentFolder.data.name} 43 | 44 | 45 | ) : ( 46 | <> 47 | history.push("/dashboard")} 54 | > 55 | Root 56 | 57 | 58 | {currentFolder.data.name} 59 | 60 | 61 | )} 62 | 63 | ); 64 | }; 65 | 66 | export default BreadCrum; 67 | -------------------------------------------------------------------------------- /src/components/Dashboard/FileComponent/FileComponent.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | text-align: left !important; 3 | width: 100vh !important; 4 | height: 100% !important; 5 | max-height: 100vh !important; 6 | } 7 | .CodeMirror-wrap pre { 8 | word-break: break-word; 9 | } 10 | .cm-wrap { 11 | height: 100vh !important; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Dashboard/FileComponent/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { faArrowLeft, faSave } from '@fortawesome/free-solid-svg-icons'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { Button, Col, Row } from 'react-bootstrap'; 5 | import { useHistory } from 'react-router-dom'; 6 | import { userFileDataUpdate } from '../../../redux/actionCreators/filefoldersActionCreators'; 7 | import { useDispatch } from 'react-redux'; 8 | 9 | const Header = ({ data, prevData, currentFile, setPrevData, setData }) => { 10 | const history = useHistory(); 11 | 12 | const dispatch = useDispatch(); 13 | const pushItBack = () => { 14 | if (data.trim() !== prevData.trim()) { 15 | if (window.confirm('are your sure to leave without saving data?')) { 16 | history.push( 17 | currentFile.data.parent === '' 18 | ? '/dashboard' 19 | : `/dashboard/folder/${currentFile.data.parent}` 20 | ); 21 | } else { 22 | return; 23 | } 24 | } else { 25 | history.push( 26 | currentFile.data.parent === '' 27 | ? '/dashboard' 28 | : `/dashboard/folder/${currentFile.data.parent}` 29 | ); 30 | } 31 | }; 32 | const saveFile = () => { 33 | setData(data + '\n'); 34 | setPrevData(data.trim()); 35 | dispatch(userFileDataUpdate(data.trim(), currentFile.docId)); 36 | }; 37 | 38 | return ( 39 | 40 | 41 |
42 | {currentFile.data.name} 43 | {data.trim() !== prevData.trim() && ' [* . Modified]'} 44 |
45 | 46 | 47 | 54 |   55 | 59 | 60 |
61 | ); 62 | }; 63 | 64 | export default Header; 65 | -------------------------------------------------------------------------------- /src/components/Dashboard/FileComponent/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Button, Col, Image, Row } from "react-bootstrap"; 3 | import { shallowEqual, useDispatch, useSelector } from "react-redux"; 4 | import { useHistory, useParams } from "react-router-dom"; 5 | import { 6 | getAdminFiles, 7 | getAdminFolders, 8 | getUserFiles, 9 | getUserFolders, 10 | } from "../../../redux/actionCreators/filefoldersActionCreators"; 11 | import "codemirror/lib/codemirror.css"; 12 | import "codemirror/theme/eclipse.css"; 13 | import "codemirror/mode/xml/xml"; 14 | import "codemirror/mode/php/php"; 15 | import "codemirror/mode/javascript/javascript"; 16 | import "codemirror/mode/textile/textile"; 17 | import "codemirror/mode/jsx/jsx"; 18 | import "codemirror/mode/css/css"; 19 | import "codemirror/mode/python/python"; 20 | import "codemirror/mode/clike/clike"; 21 | import { Controlled as CodeMirror } from "react-codemirror2"; 22 | import FileViewer from "react-file-viewer"; 23 | 24 | import "./FileComponent.css"; 25 | import Header from "./Header"; 26 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 27 | import { faDownload, faEye } from "@fortawesome/free-solid-svg-icons"; 28 | 29 | const FileComponent = () => { 30 | const { fileId } = useParams(); 31 | const history = useHistory(); 32 | 33 | const { isLoading, userId, currentFile, folders } = useSelector( 34 | (state) => ({ 35 | userId: state.auth.userId, 36 | isLoading: state.filefolders.isLoading, 37 | folders: state.filefolders.UserFolders, 38 | currentFile: state.filefolders.userFiles?.find( 39 | (file) => file.docId === fileId 40 | ), 41 | }), 42 | shallowEqual 43 | ); 44 | const [prevData, setPrevData] = useState(""); 45 | const [data, setData] = useState(""); 46 | 47 | const codes = { 48 | html: "xml", 49 | php: "php", 50 | js: "javascript", 51 | jsx: "jsx", 52 | txt: "textile", 53 | xml: "xml", 54 | css: "css", 55 | c: "clike", 56 | cpp: "clike", 57 | java: "textile", 58 | cs: "clike", 59 | py: "python", 60 | json: "javascript", 61 | }; 62 | 63 | const dispatch = useDispatch(); 64 | 65 | const extension = 66 | currentFile && 67 | currentFile.data.name.split(".")[ 68 | currentFile.data.name.split(".").length - 1 69 | ]; 70 | useEffect(() => { 71 | if (isLoading && !folders && !currentFile) { 72 | dispatch(getAdminFolders()); 73 | dispatch(getAdminFiles()); 74 | dispatch(getUserFolders(userId)); 75 | dispatch(getUserFiles(userId)); 76 | } 77 | 78 | if ( 79 | currentFile && 80 | (currentFile.data.url === "" || 81 | !currentFile.data.name.includes(".jpg") || 82 | !currentFile.data.name.includes(".png") || 83 | !currentFile.data.name.includes(".jpeg") || 84 | !currentFile.data.name.includes(".doc") || 85 | !currentFile.data.name.includes(".ppt") || 86 | !currentFile.data.name.includes(".pptx") || 87 | !currentFile.data.name.includes(".xls") || 88 | !currentFile.data.name.includes(".rar")) 89 | ) { 90 | setData(currentFile.data.data); 91 | setPrevData(currentFile.data.data); 92 | } 93 | }, [dispatch, isLoading, currentFile && currentFile.data.data]); 94 | 95 | if (isLoading) { 96 | return ( 97 | 98 | 99 |

Fetching file...

100 | 101 |
102 | ); 103 | } 104 | return ( 105 | <> 106 | {currentFile ? ( 107 | currentFile.data.url === "" && 108 | (!currentFile.data.name.includes(".jpg") || 109 | !currentFile.data.name.includes(".png") || 110 | !currentFile.data.name.includes(".jpeg") || 111 | !currentFile.data.name.includes(".doc") || 112 | !currentFile.data.name.includes(".ppt") || 113 | !currentFile.data.name.includes(".pptx") || 114 | !currentFile.data.name.includes(".xls") || 115 | !currentFile.data.name.includes(".rar")) ? ( 116 | <> 117 |
125 | 129 | 130 | { 144 | setData(value); 145 | }} 146 | style={{ textAlign: "left !important" }} 147 | /> 148 | 149 | 150 | 151 | ) : currentFile.data.name 152 | .split(".") 153 | [currentFile.data.name.split(".").length - 1].includes("png") || 154 | currentFile.data.name 155 | .split(".") 156 | [currentFile.data.name.split(".").length - 1].includes("jpg") || 157 | currentFile.data.name 158 | .split(".") 159 | [currentFile.data.name.split(".").length - 1].includes("jpeg") || 160 | currentFile.data.name 161 | .split(".") 162 | [currentFile.data.name.split(".").length - 1].includes("svg") || 163 | currentFile.data.name 164 | .split(".") 165 | [currentFile.data.name.split(".").length - 1].includes("gif") ? ( 166 | 170 | 171 |
175 |

179 | {currentFile.data.name} 180 |

181 |
182 | 188 |   189 | 195 | 196 |   Download 197 | 198 |
199 |
200 | 201 | {currentFile.data.url} 208 | 209 | 210 |
211 | ) : ( 212 | 216 | 217 |
221 |

225 | {currentFile.data.name} 226 |

227 |
228 | 234 |   235 | 241 | 242 |   Download 243 | 244 |
245 |
246 | 247 | {currentFile.data.name 248 | .split(".") 249 | [currentFile.data.name.split(".").length - 1].includes( 250 | "pdf" 251 | ) ? ( 252 | 257 | 258 |   View{" "} 259 | { 260 | currentFile.data.name.split(".")[ 261 | currentFile.data.name.split(".").length - 1 262 | ] 263 | }{" "} 264 | file 265 | 266 | ) : currentFile.data.name 267 | .split(".") 268 | [currentFile.data.name.split(".").length - 1].includes( 269 | "csv" 270 | ) || 271 | currentFile.data.name 272 | .split(".") 273 | [currentFile.data.name.split(".").length - 1].includes( 274 | "xslx" 275 | ) || 276 | currentFile.data.name 277 | .split(".") 278 | [currentFile.data.name.split(".").length - 1].includes( 279 | "docx" 280 | ) || 281 | currentFile.data.name 282 | .split(".") 283 | [currentFile.data.name.split(".").length - 1].includes( 284 | "mp4" 285 | ) || 286 | currentFile.data.name 287 | .split(".") 288 | [currentFile.data.name.split(".").length - 1].includes( 289 | "webm" 290 | ) || 291 | currentFile.data.name 292 | .split(".") 293 | [currentFile.data.name.split(".").length - 1].includes( 294 | "mp3" 295 | ) ? ( 296 | 305 |

This file is not viewable

306 | 312 | 313 |   Download 314 | 315 | 316 | } 317 | style={{ height: "100%", width: "100%" }} 318 | /> 319 | ) : ( 320 | 325 | 326 |   View{" "} 327 | { 328 | currentFile.data.name.split(".")[ 329 | currentFile.data.name.split(".").length - 1 330 | ] 331 | }{" "} 332 | file 333 | 334 | )} 335 | 336 | 337 |
338 | ) 339 | ) : ( 340 | 341 | File Not Present 342 | 343 | )} 344 | 345 | ); 346 | }; 347 | 348 | export default FileComponent; 349 | -------------------------------------------------------------------------------- /src/components/Dashboard/FolderAdminComponent/index.jsx: -------------------------------------------------------------------------------- 1 | import { faFile } from '@fortawesome/free-solid-svg-icons'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | import React, { useEffect } from 'react'; 4 | import { Col, Row } from 'react-bootstrap'; 5 | import { shallowEqual, useDispatch, useSelector } from 'react-redux'; 6 | import { useParams } from 'react-router-dom'; 7 | import { 8 | getAdminFiles, 9 | getAdminFolders, 10 | } from '../../../redux/actionCreators/filefoldersActionCreators.js'; 11 | import SubNav from '../SubNav.js/index.jsx'; 12 | 13 | const FolderAdminComponent = () => { 14 | const { folderId } = useParams(); 15 | 16 | const { files, folders, isLoading } = useSelector( 17 | (state) => ({ 18 | folders: state.filefolders.adminFolders, 19 | files: state.filefolders.adminFiles, 20 | isLoading: state.filefolders.isLoading, 21 | }), 22 | shallowEqual 23 | ); 24 | const dispatch = useDispatch(); 25 | 26 | useEffect(() => { 27 | if (isLoading && (!folders || !files)) { 28 | dispatch(getAdminFolders()); 29 | dispatch(getAdminFiles()); 30 | } 31 | }, [dispatch, isLoading]); 32 | const adminFiles = 33 | files && files.filter((file) => file.data.parent === folderId); 34 | 35 | const currentFolder = 36 | folders && folders.find((folder) => folder.docId === folderId); 37 | if (isLoading) { 38 | return ( 39 | 40 | 41 |

Fetching data...

42 | 43 |
44 | ); 45 | } 46 | return ( 47 | <> 48 | 49 | 50 | 51 |

Created Files

52 |
53 | {!files ? ( 54 |

Fetching Files....

55 | ) : ( 56 | adminFiles.map(({ data, docId }) => ( 57 | { 59 | if (e.currentTarget.classList.contains('text-white')) { 60 | e.currentTarget.style.background = '#fff'; 61 | e.currentTarget.classList.remove('text-white'); 62 | e.currentTarget.classList.remove('shadow-sm'); 63 | } else { 64 | e.currentTarget.style.background = '#017bf562'; 65 | e.currentTarget.classList.add('text-white'); 66 | e.currentTarget.classList.add('shadow-sm'); 67 | } 68 | }} 69 | key={docId} 70 | md={2} 71 | className="border h-100 mr-2 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 72 | 77 |

{data.name}

78 | 79 | )) 80 | )} 81 |
82 | 83 |
84 | 85 | ); 86 | }; 87 | 88 | export default FolderAdminComponent; 89 | -------------------------------------------------------------------------------- /src/components/Dashboard/FolderComponent/index.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | faFileAlt, 3 | faFileAudio, 4 | faFileImage, 5 | faFileVideo, 6 | faFolder, 7 | } from '@fortawesome/free-solid-svg-icons'; 8 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 9 | import React, { useEffect } from 'react'; 10 | import { Col, Row } from 'react-bootstrap'; 11 | import { shallowEqual, useDispatch, useSelector } from 'react-redux'; 12 | import { useHistory, useParams } from 'react-router-dom'; 13 | import { 14 | getAdminFiles, 15 | getAdminFolders, 16 | getUserFiles, 17 | getUserFolders, 18 | selectItem, 19 | deselectItem, 20 | deselectAll, 21 | deselctFolder, 22 | selectFolder, 23 | } from '../../../redux/actionCreators/filefoldersActionCreators.js'; 24 | import SubNav from '../SubNav.js/index.jsx'; 25 | 26 | const FolderComponent = () => { 27 | const { folderId } = useParams(); 28 | 29 | const { folders, isLoading, userId, files, selectedItems } = useSelector( 30 | (state) => ({ 31 | folders: state.filefolders.userFolders, 32 | files: state.filefolders.userFiles, 33 | isLoading: state.filefolders.isLoading, 34 | userId: state.auth.userId, 35 | selectedItems: state.filefolders.selectedItems, 36 | }), 37 | shallowEqual 38 | ); 39 | const dispatch = useDispatch(); 40 | const history = useHistory(); 41 | 42 | useEffect(() => { 43 | if (isLoading) { 44 | dispatch(getAdminFolders()); 45 | dispatch(getAdminFiles()); 46 | } 47 | if (!folders && !files) { 48 | dispatch(getUserFolders(userId)); 49 | dispatch(getUserFiles(userId)); 50 | } 51 | }, [dispatch, folders, isLoading]); 52 | const userFolders = 53 | folders && folders.filter((file) => file.data.parent === folderId); 54 | 55 | const currentFolder = 56 | folders && folders.find((folder) => folder.docId === folderId); 57 | 58 | const createdFiles = 59 | files && 60 | files.filter( 61 | (file) => file.data.parent === folderId && file.data.url === '' 62 | ); 63 | 64 | const uploadedFiles = 65 | files && 66 | files.filter( 67 | (file) => file.data.parent === folderId && file.data.url !== '' 68 | ); 69 | 70 | const isItemSelected = (docId) => { 71 | return selectedItems.find((item) => item.docId === docId) ? true : false; 72 | }; 73 | 74 | const changeRoute = (url) => { 75 | dispatch(deselectAll()); 76 | history.push(url); 77 | }; 78 | 79 | if (isLoading) { 80 | return ( 81 | 82 | 83 |

Fetching data...

84 | 85 |
86 | ); 87 | } 88 | 89 | if ( 90 | userFolders && 91 | userFolders.length < 1 && 92 | createdFiles && 93 | createdFiles.length < 1 && 94 | uploadedFiles && 95 | uploadedFiles.length < 1 96 | ) { 97 | return ( 98 | <> 99 | 100 | 101 | 102 |

Empty Folder

103 | 104 |
105 | 106 | ); 107 | } 108 | return ( 109 | <> 110 | 111 | {userFolders && userFolders.length > 0 && ( 112 | <> 113 |

Created Folders

114 | 118 | {!folders ? ( 119 |

Fetching Files....

120 | ) : ( 121 | userFolders.map(({ data, docId }) => ( 122 | 124 | changeRoute(`/dashboard/folder/${docId}`) 125 | } 126 | onClick={(e) => { 127 | if (isItemSelected(docId)) { 128 | dispatch(deselctFolder(docId)); 129 | e.currentTarget.style.background = '#fff'; 130 | e.currentTarget.classList.remove('text-white'); 131 | e.currentTarget.classList.remove('shadow-sm'); 132 | } else { 133 | // dispatch(selectItem({ docId, data, type: 'folder' })); 134 | dispatch(selectFolder({ docId, data })); 135 | e.currentTarget.style.background = '#017bf562'; 136 | e.currentTarget.classList.add('text-white'); 137 | e.currentTarget.classList.add('shadow-sm'); 138 | } 139 | }} 140 | key={docId} 141 | md={2} 142 | className="border h-100 mr-2 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 143 | 148 |

{data.name}

149 | 150 | )) 151 | )} 152 |
153 | 154 | )} 155 | {createdFiles && createdFiles.length > 0 && ( 156 | <> 157 |

Created Files

158 | 162 | {createdFiles.map(({ data, docId }) => ( 163 | changeRoute(`/dashboard/file/${docId}`)} 165 | onClick={(e) => { 166 | if (isItemSelected(docId)) { 167 | dispatch(deselectItem({ docId })); 168 | e.currentTarget.style.background = '#fff'; 169 | e.currentTarget.classList.remove('text-white'); 170 | e.currentTarget.classList.remove('shadow-sm'); 171 | } else { 172 | dispatch(selectItem({ docId, data, type: 'file' })); 173 | e.currentTarget.style.background = '#017bf562'; 174 | e.currentTarget.classList.add('text-white'); 175 | e.currentTarget.classList.add('shadow-sm'); 176 | } 177 | }} 178 | key={docId} 179 | md={2} 180 | className="border h-100 mr-2 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 181 | 186 |

{data.name}

187 | 188 | ))} 189 |
190 | 191 | )} 192 | {uploadedFiles && uploadedFiles.length > 0 && ( 193 | <> 194 |

Uploaded Files

195 | 199 | {uploadedFiles.map(({ data, docId }) => ( 200 | changeRoute(`/dashboard/file/${docId}`)} 202 | onClick={(e) => { 203 | if (isItemSelected(docId)) { 204 | dispatch(deselectItem({ docId })); 205 | e.currentTarget.style.background = '#fff'; 206 | e.currentTarget.classList.remove('text-white'); 207 | e.currentTarget.classList.remove('shadow-sm'); 208 | } else { 209 | dispatch(selectItem({ docId, data, type: 'file' })); 210 | e.currentTarget.style.background = '#017bf562'; 211 | e.currentTarget.classList.add('text-white'); 212 | e.currentTarget.classList.add('shadow-sm'); 213 | } 214 | }} 215 | key={docId} 216 | md={2} 217 | className="border h-100 mr-2 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 218 | 252 |

{data.name}

253 | 254 | ))} 255 |
256 | 257 | )} 258 | 259 | ); 260 | }; 261 | 262 | export default FolderComponent; 263 | -------------------------------------------------------------------------------- /src/components/Dashboard/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | faFileImage, 3 | faFileAlt, 4 | faFileAudio, 5 | faFileVideo, 6 | faFolder, 7 | } from '@fortawesome/free-solid-svg-icons'; 8 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 9 | import React, { useEffect } from 'react'; 10 | import { Col, Row } from 'react-bootstrap'; 11 | import { shallowEqual, useDispatch, useSelector } from 'react-redux'; 12 | import { useHistory } from 'react-router-dom'; 13 | import { 14 | getAdminFiles, 15 | getAdminFolders, 16 | getUserFiles, 17 | getUserFolders, 18 | selectItem, 19 | deselctFolder, 20 | deselectItem, 21 | deselectAll, 22 | selectFolder, 23 | } from '../../../redux/actionCreators/filefoldersActionCreators.js'; 24 | import SubNav from '../SubNav.js/index.jsx'; 25 | 26 | const Home = () => { 27 | const history = useHistory(); 28 | const dispatch = useDispatch(); 29 | const { 30 | isLoading, 31 | adminFolders, 32 | allUserFolders, 33 | userId, 34 | allUserFiles, 35 | selectedItems, 36 | } = useSelector( 37 | (state) => ({ 38 | isLoading: state.filefolders.isLoading, 39 | adminFolders: state.filefolders.adminFolders, 40 | allUserFolders: state.filefolders.userFolders, 41 | allUserFiles: state.filefolders.userFiles, 42 | userId: state.auth.userId, 43 | selectedItems: state.filefolders.selectedItems, 44 | }), 45 | shallowEqual 46 | ); 47 | 48 | const isItemSelected = (docId) => { 49 | return selectedItems.find((item) => item.docId === docId) ? true : false; 50 | }; 51 | 52 | const changeRoute = (url) => { 53 | dispatch(deselectAll()); 54 | history.push(url); 55 | }; 56 | 57 | const userFolders = 58 | allUserFolders && 59 | allUserFolders.filter((folder) => folder.data.parent === ''); 60 | 61 | const createdUserFiles = 62 | allUserFiles && 63 | allUserFiles.filter( 64 | (file) => file.data.parent === '' && file.data.url === '' 65 | ); 66 | const uploadedUserFiles = 67 | allUserFiles && 68 | allUserFiles.filter( 69 | (file) => file.data.parent === '' && file.data.url !== '' 70 | ); 71 | 72 | useEffect(() => { 73 | if (isLoading && !adminFolders) { 74 | dispatch(getAdminFolders()); 75 | dispatch(getAdminFiles()); 76 | } 77 | if (!userFolders) { 78 | dispatch(getUserFiles(userId)); 79 | dispatch(getUserFolders(userId)); 80 | } 81 | }, [dispatch, isLoading]); 82 | 83 | if (isLoading) { 84 | return ( 85 | 86 | 87 |

Fetching folders...

88 | 89 |
90 | ); 91 | } 92 | 93 | return ( 94 | <> 95 | 96 | {adminFolders && adminFolders.length > 0 && ( 97 | <> 98 |

Admin Folders

99 | 100 | {adminFolders.map(({ data, docId }) => ( 101 | 103 | history.push(`/dashboard/folder/admin/${docId}`) 104 | } 105 | onClick={(e) => { 106 | if (e.currentTarget.classList.contains('text-white')) { 107 | e.currentTarget.style.background = '#fff'; 108 | e.currentTarget.classList.remove('text-white'); 109 | e.currentTarget.classList.remove('shadow-sm'); 110 | } else { 111 | e.currentTarget.style.background = '#017bf562'; 112 | e.currentTarget.classList.add('text-white'); 113 | e.currentTarget.classList.add('shadow-sm'); 114 | } 115 | }} 116 | key={docId} 117 | md={2} 118 | className="border h-100 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 119 | 124 |

{data.name}

125 | 126 | ))} 127 |
128 | 129 | )} 130 | {userFolders && userFolders.length > 0 && ( 131 | <> 132 |

Created Folders

133 | 134 | {userFolders.map(({ data, docId }) => ( 135 | changeRoute(`/dashboard/folder/${docId}`)} 137 | onClick={(e) => { 138 | if (isItemSelected(docId)) { 139 | dispatch(deselctFolder(docId)); 140 | e.currentTarget.style.background = '#fff'; 141 | e.currentTarget.classList.remove('text-white'); 142 | e.currentTarget.classList.remove('shadow-sm'); 143 | } else { 144 | // dispatch(selectItem({ docId, data, type: 'folder' })); 145 | dispatch(selectFolder({ docId, data })); 146 | e.currentTarget.style.background = '#017bf562'; 147 | e.currentTarget.classList.add('text-white'); 148 | e.currentTarget.classList.add('shadow-sm'); 149 | } 150 | }} 151 | key={docId} 152 | md={2} 153 | className="border h-100 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 154 | 159 |

{data.name}

160 | 161 | ))} 162 |
163 | 164 | )} 165 | {createdUserFiles && createdUserFiles.length > 0 && ( 166 | <> 167 |

Created Files

168 | 169 | {createdUserFiles.map(({ data, docId }) => ( 170 | changeRoute(`/dashboard/file/${docId}`)} 172 | onClick={(e) => { 173 | if (isItemSelected(docId)) { 174 | dispatch(deselectItem({ docId })); 175 | e.currentTarget.style.background = '#fff'; 176 | e.currentTarget.classList.remove('text-white'); 177 | e.currentTarget.classList.remove('shadow-sm'); 178 | } else { 179 | dispatch(selectItem({ docId, data, type: 'file' })); 180 | e.currentTarget.style.background = '#017bf562'; 181 | e.currentTarget.classList.add('text-white'); 182 | e.currentTarget.classList.add('shadow-sm'); 183 | } 184 | }} 185 | key={docId} 186 | md={2} 187 | className="border h-100 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 188 | 193 |

{data.name}

194 | 195 | ))} 196 |
197 | 198 | )} 199 | {uploadedUserFiles && uploadedUserFiles.length > 0 && ( 200 | <> 201 |

Uploaded Files

202 | 206 | {uploadedUserFiles.map(({ data, docId }) => ( 207 | changeRoute(`/dashboard/file/${docId}`)} 209 | onClick={(e) => { 210 | if (isItemSelected(docId)) { 211 | dispatch(deselectItem({ docId })); 212 | e.currentTarget.style.background = '#fff'; 213 | e.currentTarget.classList.remove('text-white'); 214 | e.currentTarget.classList.remove('shadow-sm'); 215 | } else { 216 | dispatch(selectItem({ docId, data, type: 'file' })); 217 | e.currentTarget.style.background = '#017bf562'; 218 | e.currentTarget.classList.add('text-white'); 219 | e.currentTarget.classList.add('shadow-sm'); 220 | } 221 | }} 222 | key={docId} 223 | md={2} 224 | className="border h-100 mr-2 d-flex align-items-center justify-content-around flex-column py-1 rounded-2"> 225 | 259 |

{data.name}

260 | 261 | ))} 262 |
263 | 264 | )} 265 | 266 | ); 267 | }; 268 | 269 | export default Home; 270 | -------------------------------------------------------------------------------- /src/components/Dashboard/NavDashboard/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Nav, Navbar } from "react-bootstrap"; 3 | import { shallowEqual, useDispatch, useSelector } from "react-redux"; 4 | import { Link, useHistory } from "react-router-dom"; 5 | import { logoutUser } from "../../../redux/actionCreators/authActionCreators"; 6 | 7 | const NavDashboard = () => { 8 | const history = useHistory(); 9 | const dispatch = useDispatch(); 10 | 11 | const { isLoggedIn, user } = useSelector( 12 | (state) => ({ 13 | isLoggedIn: state.auth.isLoggedIn, 14 | user: state.auth.user, 15 | }), 16 | shallowEqual 17 | ); 18 | 19 | const logout = () => { 20 | dispatch(logoutUser()); 21 | }; 22 | 23 | return ( 24 | 30 | 35 | File Management System 36 | 37 | 85 | 86 | ); 87 | }; 88 | 89 | export default NavDashboard; 90 | -------------------------------------------------------------------------------- /src/components/Dashboard/SubNav.js/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col } from 'react-bootstrap'; 3 | import CreateFile from '../../CreateFile/index.jsx'; 4 | import CreateFolder from '../../CreateFolder/index.jsx'; 5 | import UploadFile from '../../UploadFile/index.jsx'; 6 | import BreadCrum from '../BreadCrum.js/index.jsx'; 7 | import DeleteButton from '../../DeleteButton/index.jsx'; 8 | 9 | const SubNav = ({ currentFolder }) => { 10 | return ( 11 | 14 | {currentFolder && currentFolder !== 'root folder' ? ( 15 | <> 16 | 17 | {currentFolder.data.createdBy !== 'admin' && ( 18 |
19 | 20 |   21 | 22 |   23 | 24 |   25 | 26 |
27 | )} 28 | 29 | ) : ( 30 | <> 31 |

Root

32 |
33 | 34 |   35 | 36 |   37 | 38 |   39 | 40 |
41 | 42 | )} 43 | 44 | ); 45 | }; 46 | 47 | export default SubNav; 48 | -------------------------------------------------------------------------------- /src/components/Dashboard/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Container } from "react-bootstrap"; 3 | import { shallowEqual, useSelector } from "react-redux"; 4 | import { Route, Switch, useHistory, useRouteMatch } from "react-router-dom"; 5 | 6 | import NavDashboard from "./NavDashboard"; 7 | import Home from "./Home"; 8 | import FolderAdminComponent from "./FolderAdminComponent"; 9 | import FolderComponent from "./FolderComponent"; 10 | import FileComponent from "./FileComponent"; 11 | 12 | const Dashboard = () => { 13 | const history = useHistory(); 14 | const { path } = useRouteMatch(); 15 | 16 | const { isLoggedIn } = useSelector( 17 | (state) => ({ 18 | isLoggedIn: state.auth.isLoggedIn, 19 | }), 20 | shallowEqual 21 | ); 22 | useEffect(() => { 23 | if (!isLoggedIn) { 24 | history.push("/login"); 25 | } 26 | }, [isLoggedIn]); 27 | return ( 28 | 29 | 30 | 31 | 32 | 37 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default Dashboard; 49 | -------------------------------------------------------------------------------- /src/components/DeleteButton/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; 3 | import { Button } from 'react-bootstrap'; 4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 5 | import { shallowEqual, useDispatch, useSelector } from 'react-redux'; 6 | import { deleteItems } from '../../redux/actionCreators/filefoldersActionCreators'; 7 | 8 | const DeleteButton = ({ currentFolder }) => { 9 | const dispatch = useDispatch(); 10 | const { userId, selectedItems } = useSelector( 11 | (state) => ({ 12 | userId: state.auth.userId, 13 | selectedItems: state.filefolders.selectedItems, 14 | }), 15 | shallowEqual 16 | ); 17 | 18 | const handleFileSubmit = () => { 19 | dispatch(deleteItems()); 20 | }; 21 | 22 | if (selectedItems.length === 0) { 23 | return null; 24 | } 25 | 26 | return ( 27 | 34 | ); 35 | }; 36 | 37 | export default DeleteButton; 38 | -------------------------------------------------------------------------------- /src/components/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container } from "react-bootstrap"; 3 | 4 | const Home = () => { 5 | return ; 6 | }; 7 | 8 | export default Home; 9 | -------------------------------------------------------------------------------- /src/components/Navbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Nav, Navbar } from "react-bootstrap"; 3 | import { shallowEqual, useDispatch, useSelector } from "react-redux"; 4 | import { Link, useHistory } from "react-router-dom"; 5 | import { logoutUser } from "../../redux/actionCreators/authActionCreators"; 6 | 7 | const NavbarComponent = () => { 8 | const history = useHistory(); 9 | const dispatch = useDispatch(); 10 | 11 | const { isLoggedIn, user } = useSelector( 12 | (state) => ({ 13 | isLoggedIn: state.auth.isLoggedIn, 14 | user: state.auth.user, 15 | }), 16 | shallowEqual 17 | ); 18 | 19 | const logout = () => { 20 | dispatch(logoutUser()); 21 | }; 22 | 23 | return ( 24 | 25 | 30 | File Management System 31 | 32 | 94 | 95 | ); 96 | }; 97 | 98 | export default NavbarComponent; 99 | -------------------------------------------------------------------------------- /src/components/UploadFile/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { faFileUpload, faTimes } from "@fortawesome/free-solid-svg-icons"; 3 | import { Button, Form, Modal, ProgressBar } from "react-bootstrap"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { toast } from "react-toastify"; 6 | import { shallowEqual, useDispatch, useSelector } from "react-redux"; 7 | import { storage } from "../../API/firebase"; 8 | import { addFileUser } from "../../redux/actionCreators/filefoldersActionCreators"; 9 | 10 | const UploadFile = ({ currentFolder }) => { 11 | const [showModal, setShowModal] = useState(false); 12 | const [file, setFile] = useState(null); 13 | const [fileName, setFileName] = useState(""); 14 | const [progress, setProgress] = useState(0); 15 | 16 | const dispatch = useDispatch(); 17 | const { userId, userFiles } = useSelector( 18 | (state) => ({ 19 | userId: state.auth.userId, 20 | userFiles: state.filefolders.userFiles, 21 | }), 22 | shallowEqual 23 | ); 24 | 25 | const handleFileSubmit = (e) => { 26 | e.preventDefault(); 27 | if (!file) return toast.dark("Please add file name!"); 28 | const fileExtension = file.name.split(".").reverse()[0]; 29 | const allowedExtensions = [ 30 | "html", 31 | "php", 32 | "js", 33 | "jsx", 34 | "txt", 35 | "xml", 36 | "css", 37 | "c", 38 | "cpp", 39 | "java", 40 | "cs", 41 | "py", 42 | "json", 43 | "ppt", 44 | "pptx", 45 | "docx", 46 | "png", 47 | "jpg", 48 | "jpeg", 49 | "gif", 50 | "svg", 51 | "mp3", 52 | "mp4", 53 | "webm", 54 | "pdf", 55 | ]; 56 | 57 | if (allowedExtensions.indexOf(fileExtension) === -1) { 58 | return toast.dark(`File with extension ${fileExtension} not allowed!`); 59 | } 60 | const filteredFiles = 61 | currentFolder === "root folder" 62 | ? userFiles.filter( 63 | (file) => 64 | file.data.parent === "" && 65 | file.data.name === fileName.split("\\").reverse()[0] 66 | ) 67 | : userFiles.filter( 68 | (file) => 69 | file.data.parent === currentFolder.docId && 70 | file.data.name === fileName.split("\\").reverse()[0] 71 | ); 72 | if (filteredFiles.length > 0) 73 | return toast.dark("This is alredy present in folder"); 74 | 75 | const uploadFileRef = storage.ref(`files/${userId}/${file.name}`); 76 | 77 | uploadFileRef.put(file).on( 78 | "state_change", 79 | (snapshot) => { 80 | const newProgress = 81 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100; 82 | setProgress(newProgress); 83 | }, 84 | (error) => { 85 | return toast.error(error.message); 86 | }, 87 | async () => { 88 | const url = await uploadFileRef.getDownloadURL(); 89 | if (currentFolder === "root folder") { 90 | dispatch( 91 | addFileUser({ 92 | uid: userId, 93 | parent: "", 94 | data: "", 95 | name: file.name, 96 | url: url, 97 | path: [], 98 | }) 99 | ); 100 | setFile(""); 101 | setProgress(0); 102 | setShowModal(false); 103 | return; 104 | } 105 | 106 | const path = 107 | currentFolder.data.path.length > 0 108 | ? [ 109 | ...currentFolder.data.path, 110 | { id: currentFolder.docId, name: currentFolder.data.name }, 111 | ] 112 | : [{ id: currentFolder.docId, name: currentFolder.data.name }]; 113 | 114 | dispatch( 115 | addFileUser({ 116 | uid: userId, 117 | parent: currentFolder.docId, 118 | data: "", 119 | name: file.name, 120 | url: url, 121 | path: path, 122 | }) 123 | ); 124 | setFile(""); 125 | setProgress(0); 126 | setShowModal(false); 127 | return; 128 | } 129 | ); 130 | }; 131 | 132 | return ( 133 | <> 134 | setShowModal(false)}> 135 | 136 | 137 | {progress && progress !== 100 138 | ? "Uploading..." 139 | : progress === 100 140 | ? "Uploaded" 141 | : "Upload File"} 142 | 143 | 150 | 151 | 152 | {progress && progress !== 100 ? ( 153 | 154 | ) : progress === 100 ? ( 155 |

File Uploaded Successfully

156 | ) : ( 157 |
158 | 159 | { 163 | setFileName(e.target.value); 164 | setFile(e.target.files[0]); 165 | }} 166 | custom="true" 167 | /> 168 | 169 | 170 | 177 | 178 |
179 | )} 180 |
181 |
182 | 190 | 191 | ); 192 | }; 193 | 194 | export default UploadFile; 195 | -------------------------------------------------------------------------------- /src/components/authentication/Login/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Button, Col, Container, Form, Row } from "react-bootstrap"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { Link, useHistory, useLocation } from "react-router-dom"; 5 | import { toast } from "react-toastify"; 6 | import { loginUser } from "../../../redux/actionCreators/authActionCreators"; 7 | 8 | const Login = () => { 9 | const [email, setEmail] = useState(""); 10 | const [password, setPassword] = useState(""); 11 | const [error, setError] = useState(""); 12 | 13 | const isLoggedIn = useSelector((state) => state.auth.isLoggedIn); 14 | const dispatch = useDispatch(); 15 | const history = useHistory(); 16 | const { pathname } = useLocation(); 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault(); 20 | 21 | if (!email || !password) return toast.dark("Please fill in all fields!"); 22 | const data = { 23 | email, 24 | password, 25 | }; 26 | dispatch(loginUser(data, setError)); 27 | }; 28 | 29 | useEffect(() => { 30 | if (error) { 31 | toast.error(error); 32 | } 33 | if (isLoggedIn) { 34 | history.goBack(); 35 | } 36 | }, [error]); 37 | return ( 38 | 39 | 40 | 41 |

Login

42 | 43 | 44 |
45 | 46 | setEmail(e.target.value)} 51 | /> 52 | 53 | 54 | setPassword(e.target.value)} 59 | /> 60 | 61 | 62 | 70 | 71 |

72 | Not a Member? 73 | 74 | Register 75 | 76 |

77 |
78 | 79 |
80 |
81 | ); 82 | }; 83 | 84 | export default Login; 85 | -------------------------------------------------------------------------------- /src/components/authentication/Register/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Button, Col, Container, Form, Row } from "react-bootstrap"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { Link, useHistory } from "react-router-dom"; 5 | import { toast } from "react-toastify"; 6 | import { registerUser } from "../../../redux/actionCreators/authActionCreators"; 7 | 8 | const Register = () => { 9 | const [name, setName] = useState(""); 10 | const [email, setEmail] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | const [confirmPassword, setConfirmPassword] = useState(""); 13 | const [error, setError] = useState(""); 14 | 15 | const isLoggedIn = useSelector((state) => state.auth.isLoggedIn); 16 | const dispatch = useDispatch(); 17 | const history = useHistory(); 18 | 19 | const handleSubmit = (e) => { 20 | e.preventDefault(); 21 | 22 | if (!name || !email || !password) 23 | return toast.dark("Please fill in all fields!"); 24 | 25 | if (password !== confirmPassword) 26 | return toast.dark("Passwords donot match!"); 27 | 28 | if (password.length < 8) { 29 | return toast.dark("Password must be of length 8 or more"); 30 | } 31 | if ( 32 | !/^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,16}$/.test(password) 33 | ) { 34 | return toast.dark( 35 | "Password must have alteast a number and a special character!" 36 | ); 37 | } 38 | 39 | const data = { 40 | name, 41 | email, 42 | password, 43 | }; 44 | 45 | dispatch(registerUser(data, setError)); 46 | }; 47 | 48 | useEffect(() => { 49 | if (error) { 50 | toast.error(error); 51 | } 52 | if (isLoggedIn) { 53 | history.push("/dashboard"); 54 | } 55 | }, [error, isLoggedIn]); 56 | return ( 57 | 58 | 59 | 60 |

Register

61 | 62 | 63 |
64 | 65 | setName(e.target.value)} 70 | /> 71 | 72 | 73 | setEmail(e.target.value)} 78 | /> 79 | 80 | 81 | setPassword(e.target.value)} 86 | /> 87 | 88 | 89 | setConfirmPassword(e.target.value)} 94 | /> 95 | 96 | 97 | 105 | 106 |

107 | Already a Member? 108 | 109 | Login 110 | 111 |

112 |
113 | 114 |
115 |
116 | ); 117 | }; 118 | 119 | export default Register; 120 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | 5 | import App from './App'; 6 | 7 | import 'bootstrap/dist/css/bootstrap.min.css'; 8 | import 'react-toastify/dist/ReactToastify.css'; 9 | import './index.css'; 10 | 11 | //redux 12 | import { applyMiddleware, combineReducers, createStore } from 'redux'; 13 | import { composeWithDevTools } from 'redux-devtools-extension'; 14 | import thunk from 'redux-thunk'; 15 | import { Provider } from 'react-redux'; 16 | 17 | // reducers 18 | import authReducer from './redux/reducers/authReducer'; 19 | import filefolderReducer from './redux/reducers/filefolderReducer'; 20 | 21 | const reducers = combineReducers({ 22 | auth: authReducer, 23 | filefolders: filefolderReducer, 24 | }); 25 | 26 | const store = createStore( 27 | reducers, 28 | composeWithDevTools(applyMiddleware(thunk)) 29 | ); 30 | 31 | const root = ReactDOM.createRoot(document.getElementById('root')); 32 | 33 | root.render( 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | -------------------------------------------------------------------------------- /src/models/docs.js: -------------------------------------------------------------------------------- 1 | const docModel = (uid, name, path, parent) => { 2 | const model = { 3 | createdAt: new Date(), 4 | createdBy: uid, 5 | lastAccessed: new Date(), 6 | name: name, 7 | updatedAt: new Date(), 8 | path: path, 9 | parent: parent, 10 | }; 11 | 12 | return model; 13 | }; 14 | 15 | export default docModel; 16 | -------------------------------------------------------------------------------- /src/models/files.js: -------------------------------------------------------------------------------- 1 | const fileModel = (uid, parent, data, name, url, path) => { 2 | const model = { 3 | createdAt: new Date(), 4 | createdBy: uid, 5 | data: data, 6 | name: name, 7 | parent: parent, 8 | updatedAt: new Date(), 9 | url: url, 10 | path: path, 11 | }; 12 | 13 | return model; 14 | }; 15 | 16 | export default fileModel; 17 | -------------------------------------------------------------------------------- /src/models/users.js: -------------------------------------------------------------------------------- 1 | import { database } from "../API/firebase"; 2 | 3 | const userModel = (email, name, uid) => { 4 | const model = { 5 | createdAt: database.date, 6 | docs: [], 7 | email: email, 8 | image: null, 9 | lastLogin: database.date, 10 | name: name, 11 | uid: uid, 12 | updatedAt: database.date, 13 | }; 14 | return model; 15 | }; 16 | 17 | export default userModel; 18 | -------------------------------------------------------------------------------- /src/redux/actionCreators/authActionCreators.js: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | import { auth, database } from "../../API/firebase"; 3 | import userModel from "../../models/users"; 4 | import { RESET_USER, SET_USER } from "../actions/authActions"; 5 | import { RESET_FOLDERS_FILES } from "../actions/filefoldersActions"; 6 | 7 | const setUser = (data) => ({ 8 | type: SET_USER, 9 | payload: data, 10 | }); 11 | 12 | const resetUser = () => ({ 13 | type: RESET_USER, 14 | }); 15 | 16 | export const registerUser = 17 | ({ name, email, password }, setError) => 18 | (dispatch) => { 19 | auth 20 | .createUserWithEmailAndPassword(email, password) 21 | .then((user) => { 22 | setError(""); 23 | const newUser = userModel(email, name, user.user.uid); 24 | auth.currentUser.updateProfile({ 25 | displayName: name, 26 | }); 27 | 28 | database.users.add(newUser).then((usr) => { 29 | dispatch( 30 | setUser({ 31 | userId: user.user.uid, 32 | user: { data: user.user.providerData[0] }, 33 | }) 34 | ); 35 | toast.success("User registered successfully!!"); 36 | }); 37 | }) 38 | .catch((err) => { 39 | console.log(err); 40 | if (err.code === "auth/email-already-in-use") { 41 | setError("Email Already Exists!"); 42 | } 43 | }); 44 | }; 45 | 46 | export const loginUser = 47 | ({ email, password }, setError) => 48 | (dispatch) => { 49 | auth 50 | .signInWithEmailAndPassword(email, password) 51 | .then(async (user) => { 52 | const usr = await database.users 53 | .where("uid", "==", user.user.uid) 54 | .get(); 55 | console.log(usr.docs); 56 | }) 57 | .catch(() => { 58 | setError("Invalid Email Or Password!"); 59 | }); 60 | }; 61 | 62 | export const getUser = () => (dispatch) => { 63 | auth.onAuthStateChanged(function (user) { 64 | if (user) { 65 | dispatch( 66 | setUser({ 67 | userId: auth.currentUser.uid, 68 | user: { data: auth.currentUser.providerData[0] }, 69 | }) 70 | ); 71 | } else { 72 | dispatch(resetUser()); 73 | } 74 | }); 75 | }; 76 | 77 | const reserFilesFolders = () => ({ 78 | type: RESET_FOLDERS_FILES, 79 | }); 80 | 81 | export const logoutUser = () => (dispatch) => { 82 | auth.signOut().then(() => { 83 | dispatch(resetUser()); 84 | dispatch(reserFilesFolders()); 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /src/redux/actionCreators/filefoldersActionCreators.js: -------------------------------------------------------------------------------- 1 | import { toast } from 'react-toastify'; 2 | import { database, storage } from '../../API/firebase'; 3 | import docModel from '../../models/docs'; 4 | import fileModel from '../../models/files'; 5 | import { 6 | SET_LOADING, 7 | SET_ADMIN_FILES, 8 | SET_ADMIN_FOLDERS, 9 | SET_USER_FOLDERS, 10 | ADD_USER_FOLDER, 11 | SET_USER_FILES, 12 | ADD_USER_FILE, 13 | UPDATE_USER_FILE_DATA, 14 | SELECT_ITEM, 15 | DESELECT_ITEM, 16 | DESELECT_ALL, 17 | SELECT_ITEMS, 18 | DESELECT_ITEMS, 19 | DELETE_FILES, 20 | DELETE_FOLDERS, 21 | } from '../actions/filefoldersActions'; 22 | 23 | const setLoading = (data) => ({ 24 | type: SET_LOADING, 25 | payload: data, 26 | }); 27 | const setAdminFiles = (data) => ({ 28 | type: SET_ADMIN_FILES, 29 | payload: data, 30 | }); 31 | const setAdminFolders = (data) => ({ 32 | type: SET_ADMIN_FOLDERS, 33 | payload: data, 34 | }); 35 | 36 | export const getAdminFolders = () => (dispatch) => { 37 | dispatch(setLoading(true)); 38 | 39 | database.docs 40 | .where('createdBy', '==', 'admin') 41 | .get() 42 | .then((folders) => { 43 | const allFolders = []; 44 | folders.docs.forEach((doc) => { 45 | allFolders.push({ data: doc.data(), docId: doc.id }); 46 | }); 47 | dispatch(setAdminFolders(allFolders)); 48 | dispatch(setLoading(false)); 49 | }) 50 | .catch((err) => { 51 | toast.error('Failed to fetch data!'); 52 | }); 53 | }; 54 | export const getAdminFiles = () => (dispatch) => { 55 | database.files 56 | .where('createdBy', '==', 'admin') 57 | .get() 58 | .then((files) => { 59 | const allFiles = []; 60 | files.docs.forEach((doc) => { 61 | allFiles.push({ data: doc.data(), docId: doc.id }); 62 | }); 63 | dispatch(setAdminFiles(allFiles)); 64 | }) 65 | .catch((err) => { 66 | toast.error('Failed to fetch data!'); 67 | }); 68 | }; 69 | 70 | const setUserFolders = (data) => ({ 71 | type: SET_USER_FOLDERS, 72 | payload: data, 73 | }); 74 | 75 | export const getUserFolders = (userId) => async (dispatch) => { 76 | if (userId) { 77 | database.docs 78 | .where('createdBy', '==', userId) 79 | .get() 80 | .then((folders) => { 81 | const allFolders = []; 82 | folders.docs.forEach((doc) => { 83 | allFolders.push({ data: doc.data(), docId: doc.id }); 84 | }); 85 | dispatch(setUserFolders(allFolders)); 86 | }) 87 | .catch((err) => { 88 | console.log('foldererr', err); 89 | toast.error('Failed to fetch data!'); 90 | }); 91 | } 92 | }; 93 | 94 | const addUserFolder = (data) => ({ 95 | type: ADD_USER_FOLDER, 96 | payload: data, 97 | }); 98 | 99 | export const addFolderUser = (name, userId, parent, path) => (dispatch) => { 100 | database.docs 101 | .add(docModel(userId, name, path, parent)) 102 | .then(async (doc) => { 103 | const data = await doc.get(); 104 | dispatch(addUserFolder({ data: data.data(), docId: data.id })); 105 | toast.success('Folder added Successfully!'); 106 | }) 107 | .catch((err) => { 108 | console.log(err); 109 | toast.error('Something went wrong!'); 110 | }); 111 | }; 112 | 113 | const setUserFiles = (data) => ({ 114 | type: SET_USER_FILES, 115 | payload: data, 116 | }); 117 | 118 | export const getUserFiles = (userId) => (dispatch) => { 119 | if (userId) { 120 | database.files 121 | .where('createdBy', '==', userId) 122 | .get() 123 | .then((files) => { 124 | const allFiles = []; 125 | files.docs.forEach((doc) => { 126 | allFiles.push({ data: doc.data(), docId: doc.id }); 127 | }); 128 | dispatch(setUserFiles(allFiles)); 129 | }) 130 | .catch((err) => { 131 | console.log('foldererr', err); 132 | toast.error('Failed to fetch data!'); 133 | }); 134 | } 135 | }; 136 | 137 | const addUserFile = (data) => ({ 138 | type: ADD_USER_FILE, 139 | payload: data, 140 | }); 141 | 142 | export const addFileUser = 143 | ({ uid, parent, data, name, url, path }) => 144 | (dispatch) => { 145 | database.files 146 | .add(fileModel(uid, parent, data, name, url, path)) 147 | .then(async (doc) => { 148 | const data = await doc.get(); 149 | dispatch(addUserFile({ data: data.data(), docId: data.id })); 150 | if (data.data().url === '') { 151 | toast.success('File created Successfully!'); 152 | toast.success('You can double click on the file to open the editor!'); 153 | } else { 154 | toast.success('File uploaded Successfully!'); 155 | } 156 | }) 157 | .catch((err) => { 158 | console.log(err); 159 | toast.error('Something went wrong!'); 160 | }); 161 | }; 162 | 163 | const updateUserFileData = (data) => ({ 164 | type: UPDATE_USER_FILE_DATA, 165 | payload: data, 166 | }); 167 | 168 | export const userFileDataUpdate = (data, docId) => (dispatch) => { 169 | database.files 170 | .doc(docId) 171 | .update({ 172 | updatedAt: new Date(), 173 | data: data, 174 | }) 175 | .then(() => { 176 | dispatch(updateUserFileData({ data, docId })); 177 | toast.success('Saved Successfully!!'); 178 | 179 | document.querySelector('.CodeMirror').focus(); 180 | }) 181 | .catch((err) => { 182 | console.log(err); 183 | toast.error('Something went wrong!'); 184 | }); 185 | }; 186 | 187 | export const selectItem = (data) => ({ 188 | type: SELECT_ITEM, 189 | payload: data, 190 | }); 191 | 192 | export const deselectItem = (data) => ({ 193 | type: DESELECT_ITEM, 194 | payload: data, 195 | }); 196 | 197 | export const deselectAll = () => ({ 198 | type: DESELECT_ALL, 199 | }); 200 | 201 | const selectItems = (data) => ({ 202 | type: SELECT_ITEMS, 203 | payload: data, 204 | }); 205 | 206 | const deselectItems = (data) => ({ 207 | type: DESELECT_ITEMS, 208 | payload: data, 209 | }); 210 | 211 | const deleteFilesAction = (doctIds) => ({ 212 | type: DELETE_FILES, 213 | payload: doctIds, 214 | }); 215 | 216 | export const deleteFoldersAction = (doctIds) => ({ 217 | type: DELETE_FOLDERS, 218 | payload: doctIds, 219 | }); 220 | 221 | export const getSubItems = (state, data = {}) => { 222 | const { 223 | filefolders: { userFolders, userFiles }, 224 | } = state(); 225 | 226 | const folderFiles = userFiles 227 | .filter((file) => file.data.parent === data.docId) 228 | .map((file) => ({ ...file, type: 'file' })); 229 | 230 | const subFolders = userFolders 231 | .filter( 232 | (folder) => 233 | folder.data.path.find((path) => path.id === data.docId) !== undefined 234 | ) 235 | .map((folder) => ({ ...folder, type: 'folder' })); 236 | 237 | return { 238 | folderFiles, 239 | subFolders, 240 | }; 241 | }; 242 | 243 | export const selectFolder = (data) => (dispatch, state) => { 244 | const { folderFiles, subFolders } = getSubItems(state, data); 245 | 246 | dispatch( 247 | selectItems([{ ...data, type: 'folder' }, ...folderFiles, ...subFolders]) 248 | ); 249 | }; 250 | 251 | export const deselctFolder = (docId) => (dispatch, state) => { 252 | const { folderFiles, subFolders } = getSubItems(state, { docId }); 253 | 254 | const filesDocIds = folderFiles.map((file) => file.docId); 255 | const foldersDocIds = subFolders.map((folder) => folder.docId); 256 | 257 | dispatch(deselectItems([docId, ...filesDocIds, ...foldersDocIds])); 258 | }; 259 | 260 | const deleteFiles = (files) => { 261 | const promises = files.map((file) => { 262 | if (file.data.url) { 263 | try { 264 | database.files.doc(file.docId).delete(); 265 | storage.refFromURL(file.data.url).delete(); 266 | } catch (err) { 267 | console.log(err); 268 | console.log('Failed to delete file', file.data.name); 269 | } 270 | 271 | return true; 272 | } 273 | return database.files.doc(file.docId).delete(); 274 | }); 275 | 276 | return Promise.all(promises); 277 | }; 278 | 279 | export const deleteItems = () => (dispatch, state) => { 280 | const { 281 | filefolders: { selectedItems }, 282 | } = state(); 283 | 284 | const files = selectedItems.filter((item) => item.type === 'file'); 285 | const folders = selectedItems.filter((item) => item.type === 'folder'); 286 | 287 | deleteFiles(files).then((response) => { 288 | if (response.length === files.length) { 289 | dispatch(deleteFilesAction(files.map((file) => file.docId))); 290 | } 291 | 292 | folders.forEach((folder) => { 293 | database.docs.doc(folder.docId).delete(); 294 | }); 295 | 296 | dispatch(deleteFoldersAction(folders.map((folder) => folder.docId))); 297 | dispatch(deselectAll()); 298 | 299 | toast.success('Deleted Items Successfully!'); 300 | }); 301 | }; 302 | -------------------------------------------------------------------------------- /src/redux/actions/authActions.js: -------------------------------------------------------------------------------- 1 | export const SET_USER = "SET_USER"; 2 | export const RESET_USER = "RESET_USER"; 3 | -------------------------------------------------------------------------------- /src/redux/actions/filefoldersActions.js: -------------------------------------------------------------------------------- 1 | export const SET_LOADING = 'SET_LOADING'; 2 | export const SET_ADMIN_FILES = 'SET_ADMIN_FILES'; 3 | export const SET_ADMIN_FOLDERS = 'SET_ADMIN_FOLDERS'; 4 | export const SET_USER_FOLDERS = 'SET_USER_FOLDERS'; 5 | export const ADD_USER_FOLDER = 'ADD_USER_FOLDER'; 6 | export const RESET_FOLDERS_FILES = 'RESET_FOLDERS_FILES'; 7 | export const SET_USER_FILES = 'SET_USER_FILES'; 8 | export const ADD_USER_FILE = 'ADD_USER_FILE'; 9 | export const UPDATE_USER_FILE_DATA = 'UPDATE_USER_FILE_DATA'; 10 | export const SELECT_ITEM = 'SELECT_ITEM'; 11 | export const SELECT_ITEMS = 'SELECT_ITEMS'; 12 | export const DESELECT_ITEM = 'DESELECT_ITEM'; 13 | export const DESELECT_ITEMS = 'DESELECT_ITEMS'; 14 | export const DESELECT_ALL = 'DESELECT_ALL'; 15 | export const DELETE_FILES = 'DELETE_FILES'; 16 | export const DELETE_FOLDERS = 'DELETE_FOLDERS'; 17 | -------------------------------------------------------------------------------- /src/redux/reducers/authReducer.js: -------------------------------------------------------------------------------- 1 | import { RESET_USER, SET_USER } from "../actions/authActions"; 2 | 3 | const initialState = { 4 | isLoggedIn: false, 5 | user: null, 6 | userId: null, 7 | }; 8 | 9 | const authReducer = (state = initialState, { type, payload }) => { 10 | switch (type) { 11 | case SET_USER: 12 | state = { 13 | isLoggedIn: true, 14 | user: payload.user, 15 | userId: payload.userId, 16 | }; 17 | return state; 18 | case RESET_USER: 19 | state = initialState; 20 | return state; 21 | default: 22 | return state; 23 | } 24 | }; 25 | export default authReducer; 26 | -------------------------------------------------------------------------------- /src/redux/reducers/filefolderReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_USER_FILE, 3 | ADD_USER_FOLDER, 4 | RESET_FOLDERS_FILES, 5 | SELECT_ITEM, 6 | SELECT_ITEMS, 7 | DESELECT_ITEM, 8 | DESELECT_ITEMS, 9 | DESELECT_ALL, 10 | SET_ADMIN_FILES, 11 | SET_ADMIN_FOLDERS, 12 | SET_LOADING, 13 | SET_USER_FILES, 14 | SET_USER_FOLDERS, 15 | UPDATE_USER_FILE_DATA, 16 | DELETE_FILES, 17 | DELETE_FOLDERS, 18 | } from '../actions/filefoldersActions'; 19 | 20 | const initialState = { 21 | isLoading: true, 22 | folder: 'root', 23 | userFolders: null, 24 | userFiles: null, 25 | adminFolders: null, 26 | adminFiles: null, 27 | selectedItems: [], 28 | }; 29 | 30 | const filefolderReducer = (state = initialState, { type, payload }) => { 31 | switch (type) { 32 | case SET_LOADING: 33 | state = { ...state, isLoading: payload }; 34 | return state; 35 | case SET_ADMIN_FILES: 36 | state = { ...state, adminFiles: payload }; 37 | return state; 38 | case SET_ADMIN_FOLDERS: 39 | state = { ...state, adminFolders: payload }; 40 | return state; 41 | case SET_USER_FOLDERS: 42 | state = { ...state, userFolders: payload }; 43 | return state; 44 | case ADD_USER_FOLDER: 45 | state = { ...state, userFolders: [...state.userFolders, payload] }; 46 | return state; 47 | case RESET_FOLDERS_FILES: 48 | state = initialState; 49 | return state; 50 | case SET_USER_FILES: 51 | state = { ...state, userFiles: payload }; 52 | return state; 53 | case ADD_USER_FILE: 54 | state = { ...state, userFiles: [...state.userFiles, payload] }; 55 | return state; 56 | case UPDATE_USER_FILE_DATA: 57 | const currentUserFile = state.userFiles.find( 58 | (file) => file.docId === payload.docId 59 | ); 60 | currentUserFile.data.data = payload.data; 61 | state = { 62 | ...state, 63 | userFiles: state.userFiles.map((file) => 64 | file.docId === payload.docId ? currentUserFile : file 65 | ), 66 | }; 67 | return state; 68 | case SELECT_ITEM: 69 | state = { ...state, selectedItems: [...state.selectedItems, payload] }; 70 | return state; 71 | case SELECT_ITEMS: 72 | state = { ...state, selectedItems: [...state.selectedItems, ...payload] }; 73 | return state; 74 | case DESELECT_ITEM: 75 | state = { 76 | ...state, 77 | selectedItems: state.selectedItems.filter( 78 | (item) => item.docId !== payload.docId 79 | ), 80 | }; 81 | return state; 82 | case DESELECT_ITEMS: 83 | state = { 84 | ...state, 85 | selectedItems: state.selectedItems.filter( 86 | (item) => !payload.includes(item.docId) 87 | ), 88 | }; 89 | return state; 90 | case DESELECT_ALL: 91 | state = { ...state, selectedItems: [] }; 92 | return state; 93 | case DELETE_FILES: 94 | state = { 95 | ...state, 96 | userFiles: state.userFiles.filter( 97 | (file) => !payload.includes(file.docId) 98 | ), 99 | }; 100 | return state; 101 | case DELETE_FOLDERS: 102 | state = { 103 | ...state, 104 | userFolders: state.userFolders.filter( 105 | (folder) => !payload.includes(folder.docId) 106 | ), 107 | }; 108 | return state; 109 | default: 110 | return state; 111 | } 112 | }; 113 | export default filefolderReducer; 114 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | 4 | export default defineConfig({ 5 | base: '/', 6 | plugins: [react()], 7 | }); 8 | --------------------------------------------------------------------------------