From 544a35fd9d0f93933b39757779bc508087004929 Mon Sep 17 00:00:00 2001 From: Abhinandan Wadhwa Date: Sat, 1 Oct 2022 10:19:58 +0530 Subject: [PATCH] Add Recycle Bin Functionality (#35) --- backend/routes/auth.js | 2 +- backend/routes/notes.js | 50 +++- frontend/package-lock.json | 15 + frontend/package.json | 1 + frontend/src/App.js | 7 +- .../src/components/RecycleBin/RecycleBin.css | 261 ++++++++++++++++++ .../src/components/RecycleBin/RecycleBin.js | 118 ++++++++ frontend/src/components/Sidenav/Sidenav.js | 8 +- 8 files changed, 453 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/RecycleBin/RecycleBin.css create mode 100644 frontend/src/components/RecycleBin/RecycleBin.js diff --git a/backend/routes/auth.js b/backend/routes/auth.js index bf58a52..6faa0d6 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -323,8 +323,8 @@ router.post('/forgotpassword', [ transporter.sendMail(options, (err, info) => { if (err) { + console.log(err); return res.status(400).json({ error: "Internal Server Error!" }); - console.log(error); } console.log(info.response); diff --git a/backend/routes/notes.js b/backend/routes/notes.js index 189d7e7..5ca531e 100644 --- a/backend/routes/notes.js +++ b/backend/routes/notes.js @@ -53,7 +53,7 @@ router.delete('/deletenote/:id', fetchuser, async (req, res) => { const theNote = await NoteSchema.findById(req.params.id); if (theNote.authorId === theUser.id) { - await theNote.delete(); + await theNote.update({ isDeleted: true }); return res.status(200).json({ success: "Successfully Deleted the Note" }); } else { @@ -73,7 +73,7 @@ router.delete('/deletenote/:id', fetchuser, async (req, res) => { // Route 3: Getting all user specific notes: GET: http://localhost:8181/api/notes/getallnotes. Login Required router.get('/getallnotes', fetchuser, async (req, res) => { try { - const allNotes = await NoteSchema.find({ authorId: req.user.id }).sort({ createdAt: -1 }); + const allNotes = await NoteSchema.find({ authorId: req.user.id, isDeleted: false }).sort({ createdAt: -1 }); res.status(200).json(allNotes); } catch (error) { @@ -133,4 +133,50 @@ router.put('/updatenote/:id', fetchuser, [ }); + +// ROUTE 6: Getting all the deleted notes: GET : http://localhost:8181/api/notes/bin. Login Required!! +router.get('/bin', fetchuser, async (req, res) => { + try { + const allDeletedNotes = await NoteSchema.find({ authorId: req.user.id, isDeleted: true }); + return res.status(200).json(allDeletedNotes); + + } catch (error) { + console.error(error); + return res.status(500).send("Internal Server Error"); + } +}); + + + + + + + + +// ROUTE 7: Unarchiving a Note: PUT : http://localhost:8181/api/notes/unarchive/:id. Login Required!! +router.put('/unarchive/:id', fetchuser, async (req, res) => { + try { + + const theNote = await NoteSchema.findById(req.params.id); + if (theNote.authorId === req.user.id) { + // This note belongs to this user... + await theNote.update({ isDeleted: false }); + res.status(200).json({ success: "Successfully Unarchived the Note!!" }) + } + else { + // This note doesn't belongs to this user... + return res.status(403).json({ error: "Unauthorized Access" }); + } + + } catch (error) { + console.error(error); + return res.status(500).send("Internal Server Error"); + } + + + +}); + + + module.exports = router; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8862804..0b573e0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "react": "^18.2.0", "react-detect-click-outside": "^1.1.7", "react-dom": "^18.2.0", + "react-icons": "^4.4.0", "react-js-switch": "^1.1.6", "react-router-dom": "^6.3.0", "react-scripts": "^5.0.1", @@ -13603,6 +13604,14 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -26210,6 +26219,12 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "requires": {} + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6defaf1..3252090 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "react": "^18.2.0", "react-detect-click-outside": "^1.1.7", "react-dom": "^18.2.0", + "react-icons": "^4.4.0", "react-js-switch": "^1.1.6", "react-router-dom": "^6.3.0", "react-scripts": "^5.0.1", diff --git a/frontend/src/App.js b/frontend/src/App.js index 560116a..2f66b36 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -6,8 +6,10 @@ import Notes from './components/Notes/Notes'; import ForgotPassword from './components/ForgotPassword/ForgotPassword'; import Setauthtoken from './components/Setauthtoken'; import ResetPassword from './components/ResetPassword/ResetPassword'; -import Home from './components/LandingPage/Pages/Home'; -import Contact from './components/LandingPage/Pages/Contact'; +// import LandingPage from './components/LandingPage/' +import Home from './components/LandingPage/Pages/Home' +import Contact from './components/LandingPage/Pages/Contact' +import RecycleBin from './components/RecycleBin/RecycleBin'; function App() { return ( @@ -21,6 +23,7 @@ function App() { }/> }/> }/> + }/> ); diff --git a/frontend/src/components/RecycleBin/RecycleBin.css b/frontend/src/components/RecycleBin/RecycleBin.css new file mode 100644 index 0000000..a330434 --- /dev/null +++ b/frontend/src/components/RecycleBin/RecycleBin.css @@ -0,0 +1,261 @@ +.add-box, +.icon, +.bottom-content, +.popup, +header, +.settings .menu li { + display: flex; + align-items: center; + justify-content: space-between; +} + +.add-box { + cursor: pointer; + flex-direction: column; + justify-content: center; +} + +.add-box .icon { + height: 78px; + width: 78px; + color: #3B73C6; + font-size: 40px; + border-radius: 50%; + justify-content: center; + border: 2px dashed #3B73C6; +} + +.add-box p { + color: #3B73C6; + font-weight: 500; + margin-top: 20px; +} + +.note { + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.note .details { + max-height: 165px; + overflow-y: auto; +} + +.note .details::-webkit-scrollbar, +.popup textarea::-webkit-scrollbar { + width: 0; +} + +.note .details:hover::-webkit-scrollbar, +.popup textarea:hover::-webkit-scrollbar { + width: 5px; +} + +.note .details:hover::-webkit-scrollbar-track, +.popup textarea:hover::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 25px; +} + +.note .details:hover::-webkit-scrollbar-thumb, +.popup textarea:hover::-webkit-scrollbar-thumb { + background: #e6e6e6; + border-radius: 25px; +} + +.note p { + font-size: 22px; + font-weight: 500; +} + +.note span { + display: block; + color: #575757; + font-size: 16px; + margin-top: 5px; +} + +.note .bottom-content { + padding-top: 10px; + border-top: 1px solid #ccc; +} + +.bottom-content span { + color: #6D6D6D; + font-size: 14px; +} + +.bottom-content .settings { + position: relative; +} + +.bottom-content .settings i { + color: #6D6D6D; + cursor: pointer; + font-size: 15px; +} + +.settings .menu { + z-index: 1; + bottom: 0; + right: -5px; + padding: 5px 0; + background: #fff; + position: absolute; + border-radius: 4px; + transform: scale(0); + transform-origin: bottom right; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); + transition: transform 0.2s ease; +} + +.settings.show .menu { + transform: scale(1); +} + +.settings .menu li { + height: 25px; + font-size: 16px; + margin-bottom: 2px; + padding: 17px 15px; + cursor: pointer; + box-shadow: none; + border-radius: 0; + justify-content: flex-start; +} + +.menu li:last-child { + margin-bottom: 0; +} + +.menu li:hover { + background: #f5f5f5; +} + +.menu li i { + padding-right: 8px; +} + +.popup-box { + position: fixed; + top: 0; + left: 0; + z-index: 2; + height: 100%; + width: 100%; + background: rgba(0, 0, 0, 0.4); +} + +.popup-box .popup { + position: absolute; + top: 50%; + left: 50%; + z-index: 3; + width: 100%; + max-width: 400px; + justify-content: center; + transform: translate(-50%, -50%) scale(0.95); +} + +.popup-box, +.popup { + opacity: 0; + pointer-events: none; + transition: all 0.25s ease; +} + +.popup-box.show, +.popup-box.show .popup { + opacity: 1; + pointer-events: auto; +} + +.popup-box.show .popup { + transform: translate(-50%, -50%) scale(1); +} + +.popup .content { + border-radius: 5px; + background: #fff; + width: calc(100% - 15px); + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); +} + +.content header { + padding: 15px 25px; + border-bottom: 1px solid #ccc; +} + +.content header p { + font-size: 20px; + font-weight: 500; +} + +.content header i { + color: #8b8989; + cursor: pointer; + font-size: 23px; +} + +.content form { + margin: 15px 25px 35px; +} + +.content form .row { + margin-bottom: 20px; +} + +form .row label { + font-size: 18px; + display: block; + margin-bottom: 6px; +} + +form :where(input, textarea) { + height: 50px; + width: 100%; + outline: none; + font-size: 17px; + padding: 0 15px; + border-radius: 4px; + border: 1px solid #999; +} + +form :where(input, textarea):focus { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.11); +} + +form .row textarea { + height: 150px; + resize: none; + padding: 8px 15px; +} + +form button { + width: 100%; + height: 50px; + color: #fff; + outline: none; + border: none; + cursor: pointer; + font-size: 17px; + border-radius: 4px; + background: #3B73C6; +} + +@media (max-width: 660px) { + .wrapper { + margin: 15px; + gap: 15px; + grid-template-columns: repeat(auto-fill, 100%); + } + + .popup-box .popup { + max-width: calc(100% - 15px); + } + + .bottom-content .settings i { + font-size: 17px; + } +} \ No newline at end of file diff --git a/frontend/src/components/RecycleBin/RecycleBin.js b/frontend/src/components/RecycleBin/RecycleBin.js new file mode 100644 index 0000000..8090abb --- /dev/null +++ b/frontend/src/components/RecycleBin/RecycleBin.js @@ -0,0 +1,118 @@ +import React, { useEffect, useState } from 'react' +import LoadingBar from 'react-top-loading-bar' +import Sidenav from '../Sidenav/Sidenav' +import { ToastContainer, toast } from 'react-toastify'; +import { useNavigate } from 'react-router-dom'; +import { RiInboxUnarchiveLine } from 'react-icons/ri'; + +const RecycleBin = () => { + const [progress, setProgress] = useState(0); + const [notesList, setNotesList] = useState([]); + + const navigate = useNavigate(); + + const getDeletedNotes = async (authtoken) => { + setProgress(20); + const response = await fetch('http://localhost:8181/api/notes/bin', { + + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'auth-token': authtoken + } + }); + + setProgress(50); + + const json = await response.json(); + + setProgress(70); + + setNotesList(json); + + setProgress(100); + } + + + useEffect(() => { + const authtoken = sessionStorage.getItem('auth-token'); + if (authtoken) { + getDeletedNotes(authtoken); + } + else { + navigate('/login'); + } + }, []) + + + + const convertToMonthName = (num) => { + var months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; + + return months[num]; + } + + + + const unArchive = async (id) => { + const authtoken = sessionStorage.getItem('auth-token'); + const response = await fetch(`http://localhost:8181/api/notes/unarchive/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'auth-token': authtoken + } + }); + + const json = await response.json(); + // setNotesList(notesList.filter()) + await getDeletedNotes(authtoken); + } + + + return ( + <> + setProgress(0)} + /> + +
+
Recycle Bin
+ +
+ + + {notesList && notesList.map((note) => { + const dateStu = note.createdAt; + return ( +
  • +
    +

    {note.title}

    + {note.description} +
    +
    + {convertToMonthName(new Date(dateStu).getMonth()) + " " + new Date(dateStu).getDate().toString() + ", " + new Date(dateStu).getFullYear()} +
    unArchive(note._id)} id={`settings-${note._id}`} className="settings"> + + {/* openMenu(note._id)} className="uil uil-ellipsis-h"> +
      +
    • openAddNoteModalForEditNote(note._id)}>Edit
    • +
    • deleteNote(note._id)}>Delete
    • +
    */} +
    +
    +
  • + ); + })} + + +
    + +
    + + ) +} + +export default RecycleBin \ No newline at end of file diff --git a/frontend/src/components/Sidenav/Sidenav.js b/frontend/src/components/Sidenav/Sidenav.js index 43f6525..4071716 100644 --- a/frontend/src/components/Sidenav/Sidenav.js +++ b/frontend/src/components/Sidenav/Sidenav.js @@ -1,6 +1,6 @@ import React, { useContext } from 'react' import './Sidenav.css' -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import logo from '../../assets/logo.png' import GlobalContext from '../../context/GlobalContext'; @@ -84,10 +84,10 @@ const Sidenav = () => {
  • - + - Soon.. - + Bin +