├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── auto-label.yml └── detect-duplicate-issues.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── backend ├── .env.example ├── .gitignore ├── Makefile ├── Routes │ ├── Router.js │ ├── eventRoutes.js │ ├── noteRoutes.js │ ├── uploads │ │ ├── profilePhoto-1729164520517-905517046.jpg │ │ ├── profilePhoto-1729164538736-40559474.jpg │ │ └── profilePhoto-1729164567074-811265604.jpg │ └── user.routes.js ├── controllers │ ├── noteControllers.js │ └── user.controllers.js ├── index.js ├── mail │ ├── contactUsMailSender.js │ ├── forgotPAsswordOtpMail.js │ └── sendMail.js ├── middlewares │ └── user.middlewares.js ├── models │ ├── contactModel.js │ ├── eventModel.js │ ├── feedbackModel.js │ ├── noteModel.js │ └── userModel.js ├── package-lock.json ├── package.json ├── uploads │ ├── profilePhoto-1721713869259-990478442.png │ ├── profilePhoto-1721713968989-129215121.jpeg │ └── profilePhoto-1721714084138-572334207.jpg ├── utilities.js ├── utils │ ├── const.js │ └── multer.js └── vercel.json ├── frontend ├── .env.development ├── .env.example ├── .eslintrc.cjs ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── logo.png │ └── testimonial-section.png ├── src │ ├── App.css │ ├── App.jsx │ ├── assets │ │ └── images │ │ │ ├── add-notes.svg │ │ │ ├── addPost.svg │ │ │ ├── cross.svg │ │ │ ├── hero1.jpg │ │ │ ├── hero2.png │ │ │ ├── hero3.png │ │ │ ├── logo │ │ │ ├── dark-logo.jpg │ │ │ └── whiteLogo.jpg │ │ │ ├── no-data.svg │ │ │ └── noFound.svg │ ├── components │ │ ├── About │ │ │ └── About.jsx │ │ ├── ArchivedNotes │ │ │ └── ArchivedNotes.jsx │ │ ├── Brands.jsx │ │ ├── Calendar │ │ │ └── Calendar.jsx │ │ ├── Cards │ │ │ ├── NoteCard.jsx │ │ │ └── ProfileInfo.jsx │ │ ├── CircularLoader.jsx │ │ ├── Contact │ │ │ └── Contact.jsx │ │ ├── Contributors │ │ │ ├── Contributors.css │ │ │ └── Contributors.jsx │ │ ├── EmptyCard │ │ │ └── EmptyCard.jsx │ │ ├── ErrorPage.jsx │ │ ├── Faq.jsx │ │ ├── FilterTags.jsx │ │ ├── Footer.jsx │ │ ├── Footer │ │ │ └── logo.png │ │ ├── GoogleTranslate.jsx │ │ ├── Hero │ │ │ ├── Hero.jsx │ │ │ └── styles.css │ │ ├── Input │ │ │ ├── AddAttachmentInput.jsx │ │ │ ├── PasswordInput.jsx │ │ │ └── TagInput.jsx │ │ ├── Loading.jsx │ │ ├── Modal.jsx │ │ ├── Navbar-2.jsx │ │ ├── Navbar.jsx │ │ ├── Preloader.jsx │ │ ├── Pricing.jsx │ │ ├── Progressbar.jsx │ │ ├── Scroll-on-top │ │ │ └── ScrollOnTop.jsx │ │ ├── SearchBar │ │ │ └── SearchBar.jsx │ │ ├── Tabs.jsx │ │ ├── Testimonial.jsx │ │ ├── Toggle.jsx │ │ └── sticky_footer │ │ │ ├── Content.jsx │ │ │ └── Footer.jsx │ ├── hooks │ │ └── noteActions.jsx │ ├── index.css │ ├── main.jsx │ ├── pages │ │ ├── ForgotPassword │ │ │ ├── NewPassword.jsx │ │ │ ├── VerifyEmail.jsx │ │ │ └── VerifyOtp.jsx │ │ ├── Home │ │ │ ├── AddEditNotes.jsx │ │ │ ├── Home.jsx │ │ │ └── Templates.jsx │ │ ├── Login │ │ │ └── Login.jsx │ │ ├── ProfilePage │ │ │ └── ProfilePage.jsx │ │ └── Signup │ │ │ └── Signup.jsx │ ├── setupProxy.js │ └── utils │ │ ├── ProtectedRoute.jsx │ │ ├── axiosInstance.js │ │ └── helper.js ├── tailwind.config.js └── vite.config.js ├── package-lock.json ├── package.json └── todo /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue Template 2 | 3 | ## Description 4 | 5 | A clear and concise description of what the issue is. Include any relevant information such as version, environment, or conditions where the issue was observed. 6 | 7 | ## Steps to Reproduce 8 | 9 | Please provide step-by-step instructions on how to reproduce the issue: 10 | 11 | 1. Go to '...' 12 | 2. Click on '...' 13 | 3. Scroll down to '...' 14 | 4. See error 15 | 16 | ## Expected Behavior 17 | 18 | Describe what you expected to happen. 19 | 20 | ## Actual Behavior 21 | 22 | Describe what actually happens when you follow the steps. 23 | 24 | ## Screenshots 25 | 26 | If applicable, add screenshots to help explain the issue. 27 | 28 | ## Additional Context 29 | 30 | Add any other context or information about the issue here. 31 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed. Also include relevant motivation and context. 4 | 5 | - Fixes #(issue number) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] Documentation update 15 | - [ ] Tests update 16 | 17 | ## Checklist 18 | 19 | - [ ] My code follows the style guidelines of this project 20 | - [ ] I have performed a self-review of my own code 21 | - [ ] I have commented my code, particularly in hard-to-understand areas 22 | - [ ] My changes generate no new warnings 23 | - [ ] New and existing unit tests pass locally with my changes 24 | - [ ] I have maintained a clean commit history by using the necessary Git commands 25 | - [ ] I have checked that my code does not cause any merge conflicts 26 | 27 | ## Screenshots (if applicable) 28 | 29 | Add screenshots to help explain the changes (if necessary). -------------------------------------------------------------------------------- /.github/auto-label.yml: -------------------------------------------------------------------------------- 1 | name: Auto Label Issue/PR 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened, edited] 6 | pull_request: 7 | types: [opened, reopened, edited] 8 | 9 | jobs: 10 | label_issue_pr: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | steps: 16 | - name: Label Issue/PR 17 | uses: actions/github-script@v6 18 | with: 19 | github-token: ${{secrets.GITHUB_TOKEN}} 20 | script: | 21 | const isIssue = context.payload.issue !== undefined; 22 | const item = isIssue ? context.payload.issue : context.payload.pull_request; 23 | const itemBody = item.body ? item.body.toLowerCase() : ''; 24 | const itemTitle = item.title.toLowerCase(); 25 | 26 | // Add labels to both issues and pull requests 27 | await github.rest.issues.addLabels({ 28 | owner: context.repo.owner, 29 | repo: context.repo.repo, 30 | issue_number: item.number, 31 | labels: ['gssoc-ext', 'hacktoberfest-accepted'] 32 | }); 33 | 34 | const addLabel = async (label) => { 35 | await github.rest.issues.addLabels({ 36 | owner: context.repo.owner, 37 | repo: context.repo.repo, 38 | issue_number: item.number, 39 | labels: [label] 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /.github/detect-duplicate-issues.yml: -------------------------------------------------------------------------------- 1 | name: Duplicate Issue Detector 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | detect-duplicate: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Check out repository 13 | uses: actions/checkout@v2 14 | 15 | - name: Detect duplicate issues 16 | id: detect 17 | uses: actions-cool/issues-similarity-analysis@v1 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | owner: ${{ github.repository_owner }} 21 | repo: ${{ github.event.repository.name }} 22 | threshold: 0.7 # Similarity threshold; adjust as needed 23 | label: duplicate 24 | close: true 25 | 26 | - name: Comment on Duplicate Issue 27 | if: steps.detect.outputs.duplicate == 'true' 28 | run: | 29 | curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 30 | -H "Accept: application/vnd.github.v3+json" \ 31 | https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments \ 32 | -d '{"body": "This issue is a duplicate of #${{ steps.detect.outputs.original_issue_number }}. Please refer to the original issue for updates."}' 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /frontend/.env 2 | /backend/.env 3 | todo 4 | node_modules 5 | /frontend/node_modules 6 | /backend/node_modules 7 | .env 8 | .env.development -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 🫱🏻‍🫲🏻 2 | 3 | ## 1. Purpose 4 | At Scribbie, we aim to create an open and welcoming environment where contributors from all backgrounds feel valued and respected. This code of conduct outlines expectations for behavior to ensure everyone can contribute in a harassment-free environment. 5 | 6 | ## 2. Scope 7 | This Code of Conduct applies to all contributors, including maintainers and users. It applies to both project spaces and public spaces where an individual represents the project. 8 | 9 | ## 3. Our Standards 10 | In a welcoming environment, contributors should: 11 | 12 | Be kind and respectful to others. 13 | Collaborate with others in a constructive manner. 14 | Provide feedback in a respectful and considerate way. 15 | Be open to differing viewpoints and experiences. 16 | Show empathy and understanding towards others. 17 | Unacceptable behaviors include, but are not limited to: 18 | 19 | Use of derogatory comments, insults, or personal attacks. 20 | Harassment of any kind, including but not limited to: offensive comments related to gender, race, religion, or any other personal characteristics. 21 | The publication of private information without consent. 22 | Any behavior that could be perceived as discriminatory, intimidating, or threatening. 23 | 24 | ## 4. Enforcement 25 | Instances of unacceptable behavior may be reported to the project team. All complaints will be reviewed and investigated and will result in appropriate action. 26 | 27 | ## 5. Acknowledgment 28 | By contributing to Scribbie, you agree to adhere to this Code of Conduct and help create a safe, productive, and inclusive environment for all. 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Scribbie 🎉 2 | 3 | Thank you for considering contributing to Scribbie! We welcome all contributions, whether you're fixing bugs, improving documentation, or adding new features. To help you get started, we've outlined the process for contributing to the project. 4 | 5 |
6 | 7 | # Code of Conduct 📃 8 | 9 | Please read and follow our [Code of Conduct.](https://github.com/Scribbie-Notes/notes-app/blob/main/CODE_OF_CONDUCT.md) 10 | 11 |
12 | 13 | #

Star our Repository ⭐

14 | 15 | ###
[![Stars](https://img.shields.io/github/stars/Scribbie-Notes/notes-app?style=for-the-badge&logo=github)](https://github.com/Scribbie-Notes/notes-app/stargazers) [![Forks](https://img.shields.io/github/forks/Scribbie-Notes/notes-app?style=for-the-badge&logo=github)](https://github.com/Scribbie-Notes/notes-app/network/members) [![Issues](https://img.shields.io/github/issues/Scribbie-Notes/notes-app?style=for-the-badge&logo=github)](https://github.com/Scribbie-Notes/notes-app/issues) [![PRs Open](https://img.shields.io/github/issues-pr/Scribbie-Notes/notes-app?style=for-the-badge&logo=github)](https://github.com/Scribbie-Notes/notes-app/pulls) [![PRs Closed](https://img.shields.io/github/issues-pr-closed/Scribbie-Notes/notes-app?style=for-the-badge&logo=github&color=2cbe4e)](https://github.com/Scribbie-Notes/notes-app/pulls?q=is%3Apr+is%3Aclosed)
16 | 17 |
18 | 19 | # Need Help With The Basics? 🤔 20 | 21 | If you're new to Git and GitHub, no worries! Here are some useful resources: 22 | 23 | - [Forking a Repository](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) 24 | - [Cloning a Repository](https://help.github.com/en/desktop/contributing-to-projects/creating-an-issue-or-pull-request) 25 | - [How to Create a Pull Request](https://opensource.com/article/19/7/create-pull-request-github) 26 | - [Getting Started with Git and GitHub](https://towardsdatascience.com/getting-started-with-git-and-github-6fcd0f2d4ac6) 27 | - [Learn GitHub from Scratch](https://docs.github.com/en/get-started/start-your-journey/git-and-github-learning-resources) 28 | 29 |
30 | 31 | # Project Structure 📂 32 | 33 | ```bash 34 | NOTES-APP/ 35 | ├── .github/ # GitHub-related configurations such as workflows, issue templates, etc 36 | │ 37 | ├── backend/ # All the backend development files included here 38 | │ 39 | ├── frontend/ # All the frontend development files included here 40 | │ 41 | ├── .gitignore 42 | │ 43 | ├── CODE_OF_CONDUCT.md # Some rules for the contributors 44 | │ 45 | ├── CONTRIBUTING.md # Instructions for the contributors 46 | │ 47 | ├── package-lock.json 48 | │ 49 | ├── package.json 50 | │ 51 | ├── README.md # Some instructions about contributions 52 | │ 53 | ├── todo 54 | ``` 55 | 56 |
57 | 58 | # Contribution Guidelines 📚 59 | 60 | - Follow the coding standards and guidelines. 61 | - Write meaningful commit messages. 62 | - Ensure your changes do not introduce breaking bugs. 63 | - Be responsive to feedback and comments from the project maintainers. 64 | - Respect deadlines and guidelines in case you're working as part of a program (e.g., GSSoC). 65 | 66 | We appreciate your contributions and look forward to collaborating with you to make Scribbie a better tool! 67 | 68 |
69 | 70 | # First Pull Request ✨ 71 | 72 | 1. **Star this repository** 73 | Click on the top right corner marked as **Stars** at last. 74 | 75 | 2. **Fork this repository** 76 | Click on the top right corner marked as **Fork** at second last. 77 | 78 | 3. **Clone the forked repository** 79 | 80 | ```bash 81 | git clone https://github.com//notes-app.git 82 | ``` 83 | 84 | 4. **Navigate to the project directory** 85 | 86 | ```bash 87 | cd notes-app 88 | ``` 89 | 90 | 5. **Create a new branch** 91 | 92 | ```bash 93 | git checkout -b 94 | ``` 95 | 96 | 6. **To make changes** 97 | 98 | ```bash 99 | git add . 100 | ``` 101 | 102 | 7. **Now to commit** 103 | 104 | ```bash 105 | git commit -m "add comment according to your changes or addition of features inside this" 106 | ``` 107 | 108 | 8. **Push your local commits to the remote repository** 109 | 110 | ```bash 111 | git push -u origin 112 | ``` 113 | 114 | 9. **Create a Pull Request** 115 | 116 | 10. **Congratulations! 🎉 you've made your contribution** 117 | 118 |
119 | 120 | # Alternatively, contribute using GitHub Desktop 🖥️ 121 | 122 | 1. **Open GitHub Desktop:** 123 | Launch GitHub Desktop and log in to your GitHub account if you haven't already. 124 | 125 | 2. **Clone the Repository:** 126 | - If you haven't cloned the project repository yet, you can do so by clicking on the "File" menu and selecting "Clone Repository." 127 | - Choose the project repository from the list of repositories on GitHub and clone it to your local machine. 128 | 129 | 3.**Switch to the Correct Branch:** 130 | - Ensure you are on the branch that you want to submit a pull request for. 131 | - If you need to switch branches, you can do so by clicking on the "Current Branch" dropdown menu and selecting the desired branch. 132 | 133 | 4. **Make Changes:** 134 | - Make your changes to the code or files in the repository using your preferred code editor. 135 | 136 | 5. **Commit Changes:** 137 | - In GitHub Desktop, you'll see a list of the files you've changed. Check the box next to each file you want to include in the commit. 138 | - Enter a summary and description for your changes in the "Summary" and "Description" fields, respectively. Click the "Commit to " button to commit your changes to the local branch. 139 | 140 | 6. **Push Changes to GitHub:** 141 | - After committing your changes, click the "Push origin" button in the top right corner of GitHub Desktop to push your changes to your forked repository on GitHub. 142 | 143 | 7. **Create a Pull Request:** 144 | - Go to the GitHub website and navigate to your fork of the project repository. 145 | - You should see a button to "Compare & pull request" between your fork and the original repository. Click on it. 146 | 147 | 8. **Review and Submit:** 148 | - On the pull request page, review your changes and add any additional information, such as a title and description, that you want to include with your pull request. 149 | - Once you're satisfied, click the "Create pull request" button to submit your pull request. 150 | 151 | 9. **Wait for Review:** 152 | Your pull request will now be available for review by the project maintainers. They may provide feedback or ask for changes before merging your pull request into the main branch of the project repository. 153 | 154 |
155 | 156 | # Good Coding Practices 🧑‍💻 157 | 158 | 1. **Follow the Project's Code Style** 159 | 160 | - Maintain consistency with the existing code style (indentation, spacing, comments). 161 | - Use meaningful and descriptive names for variables, functions, and classes. 162 | - Keep functions short and focused on a single task. 163 | - Avoid hardcoding values; instead, use constants or configuration files when possible. 164 | 165 | 2. **Write Clear and Concise Comments** 166 | 167 | - Use comments to explain why you did something, not just what you did. 168 | - Avoid unnecessary comments that state the obvious. 169 | - Document complex logic and functions with brief explanations to help others understand your thought -process. 170 | 171 | 3. **Keep Code DRY (Don't Repeat Yourself)** 172 | 173 | - Avoid duplicating code. Reuse functions, methods, and components whenever possible. 174 | - If you find yourself copying and pasting code, consider creating a new function or component. 175 | 176 | 4. **Write Tests** 177 | 178 | - Write unit tests for your functions and components. 179 | - Ensure your tests cover both expected outcomes and edge cases. 180 | - Run tests locally before making a pull request to make sure your changes don’t introduce new bugs. 181 | 182 | 5. **Code Reviews and Feedback** 183 | 184 | - Be open to receiving constructive feedback from other contributors. 185 | - Conduct code reviews for others and provide meaningful suggestions to improve the code. 186 | - Always refactor your code based on feedback to meet the project's standards. 187 | 188 |
189 | 190 | # Pull Request Process 🚀 191 | 192 | When submitting a pull request, please adhere to the following: 193 | 194 | 1. **Self-review your code** before submission. 😀 195 | 2. Include a detailed description of the functionality you’ve added or modified. 196 | 3. Comment your code, especially in complex sections, to aid understanding. 197 | 4. Add relevant screenshots to assist in the review process. 198 | 5. Submit your PR using the provided template and hang tight; we'll review it as soon as possible! 🚀 199 | 200 |
201 | 202 | # Issue Report Process 📌 203 | 204 | To report an issue, follow these steps: 205 | 206 | 1. Navigate to the project's issues section :- [Issues](https://github.com/Scribbie-Notes/notes-app/issues/new) 207 | 2. Please kindly choose the appropriate template according to your issue. 208 | 3. Provide a clear and concise description of the issue. 209 | 4. Wait until someone looks into your report. 210 | 5. Begin working on the issue only after you have been assigned to it. 🚀 211 | 212 |
213 | 214 | # Thank you for contributing 💗 215 | 216 | We truly appreciate your time and effort to help improve our project. Feel free to reach out if you have any questions or need guidance. Happy coding! 🚀 217 | 218 | ## -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Scribbie Notes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

**📚 Scribbie Notes App 📚** 2 | 3 |

4 | 5 | **Scribbie** is a powerful, intuitive note-taking website built specifically for working professionals who want to manage their notes seamlessly and effectively. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
🌟 Stars🍴 Forks🐛 Issues🔔 Open PRs🔕 Close PRs
StarsForksIssuesOpen Pull RequestsClosed Pull Requests
26 | 27 | --- 28 | 29 | ## 🔥 Tech Stack 30 | 31 | - **Frontend**: React.js, TailwindCSS, React Hot Toast 32 | - **Backend**: Node.js, Express.js, MongoDB 33 | - **Authentication**: Google Auth 34 | - **Deployment**: Vercel 35 | 36 | --- 37 | 38 | ## 💻 Getting Started 39 | 40 | ### Prerequisites 41 | 42 | Before you begin, ensure that you have the following installed: 43 | 44 | 1. **Node.js**: [Download Node.js](https://nodejs.org) 45 | 2. **MongoDB**: [Download MongoDB](https://www.mongodb.com) 46 | 3. **Git**: [Download Git](https://git-scm.com) 47 | 48 | ### 🚀 Running Scribbie on Your Local Machine 49 | 50 | Follow these steps to get Scribbie running on your local machine: 51 | 52 | 1. **Clone the repository**: 53 | ```bash 54 | git clone https://github.com/yashmandi/notes-app.git 55 | ``` 56 | 57 | 2. **Navigate to the project directory**: 58 | ```bash 59 | cd notes-app 60 | ``` 61 | 62 | 3. **Install dependencies for both backend and frontend**: 63 | 64 | - Install backend dependencies: 65 | ```bash 66 | cd backend 67 | npm install 68 | ``` 69 | 70 | - Install frontend dependencies: 71 | ```bash 72 | cd ../frontend 73 | npm install 74 | ``` 75 | 76 | 4. **Set up environment variables**: Create `.env` files in both backend and frontend directories with the necessary values. 77 | Here's a sample configuration: 78 | 79 | **Backend `.env`:** 80 | ```bash 81 | # Backend Environment Variables 82 | PORT=5000 83 | MONGODB_URI=mongodb://localhost:27017/your-database-name 84 | GOOGLE_API_TOKEN=your_google_api_token_here 85 | ``` 86 | 87 | **Frontend `.env`:** 88 | ```bash 89 | # Frontend Environment Variables 90 | VITE_BACKEND_URL=http://localhost:5000 91 | VITE_REACT_APP_GOOGLE_API_TOKEN=your_google_api_token_here 92 | ``` 93 | 94 | 5. **Run the project**: 95 | - Run the backend server: 96 | ```bash 97 | cd backend 98 | npm start 99 | ``` 100 | - Run the frontend: 101 | ```bash 102 | cd ../frontend 103 | npm run dev 104 | ``` 105 | 106 | --- 107 | 108 | # 📚 Contribution Guidelines 109 | We welcome contributions from the community! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for detailed guidelines. 110 | 111 | --- 112 | 113 | 114 | # 👀 Our Contributors 115 | 116 | - We extend our heartfelt gratitude for your invaluable contribution to our project! Your efforts play a pivotal role in elevating Ratna-Supermarket to greater heights. 117 | - Make sure you show some love by giving ⭐ to our repository. 118 | 119 |
120 | 121 | 122 | 123 | 124 |
125 | 126 | --- 127 | 128 | # 🤝 Code of Conduct 129 | All contributors must adhere to our [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) to ensure a positive collaboration environment. 130 | 131 | --- 132 | 133 | 🎉 Happy Contributing! 134 | Let’s work together to make Scribbie an even better tool! 135 | 136 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Security Policy 2 | Supported Versions 3 | The following versions of Notes-App are currently supported with security updates: 4 | 5 | Version Supported 6 | 1.x.x ✅ Supported 7 | 0.x.x ❌ Not supported 8 | Reporting a Vulnerability 9 | If you discover a security vulnerability in Notes-App, we encourage you to report it as soon as possible. We will investigate all legitimate reports and do our best to quickly fix the issue. 10 | 11 | # How to Report 12 | Please report vulnerabilities by emailing us at notesapp@gmail.com. Include as much detail as possible to help us identify and fix the issue swiftly. 13 | Do not share the vulnerability publicly until it has been addressed and a patch is available. 14 | Security Updates 15 | We will notify users via GitHub releases for any critical security updates. 16 | Minor security patches will be included in regular updates as needed. 17 | 18 | # Security Best Practices 19 | Make sure to use the latest version of Notes-App for the latest security features and patches. 20 | Follow password best practices, such as using strong, unique passwords for each account. 21 | Regularly update your dependencies to the latest versions. 22 | 23 | # Acknowledgements 24 | We appreciate contributions from the community and researchers who help us improve the security of Notes-App. Thank you for keeping the platform secure for everyone! 25 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # BREVO_USER="" 2 | # BREVO_PASSWORD="" 3 | # BREVO_HOST="smtp-relay.brevo.com" 4 | # BREVO_PORT=587 5 | 6 | 7 | # MONGO_URI="" 8 | # PORT=3000 9 | # ACCESS_TOKEN_SECRET="" 10 | # GOOGLE_API_TOKEN="" 11 | 12 | 13 | PORT=5000 14 | MONGODB_URI=ADD_YOUR_MONGODB_URI_HERE 15 | GOOGLE_API_TOKEN=your_google_api_token_here -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .env.* 2 | .env 3 | !.env.example 4 | 5 | node_modules -------------------------------------------------------------------------------- /backend/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | npm start & 3 | cd ../frontend && npm run dev 4 | -------------------------------------------------------------------------------- /backend/Routes/eventRoutes.js: -------------------------------------------------------------------------------- 1 | // routes/event.js 2 | import { Router } from "express"; 3 | import Event from "../models/eventModel.js"; 4 | import { authenticationToken } from "../middlewares/user.middlewares.js"; 5 | 6 | const eventRoutes = Router(); 7 | 8 | // Middleware to extract user from request 9 | const getUserFromRequest = (req) => req.user?.user; 10 | 11 | // Create an event 12 | eventRoutes.post('/add/event', authenticationToken, async (req, res) => { 13 | const { date, title, color, description } = req.body; 14 | const user = getUserFromRequest(req); 15 | 16 | if (!user) { 17 | return res.status(401).json({ message: 'Unauthorized' }); 18 | } 19 | 20 | try { 21 | const event = new Event({ date, title, color, description, userId: user._id }); 22 | await event.save(); 23 | res.status(201).json(event); 24 | } catch (error) { 25 | res.status(400).json({ message: error.message }); 26 | } 27 | }); 28 | 29 | // Get all events 30 | eventRoutes.get('/get/events', authenticationToken, async (req, res) => { 31 | const user = getUserFromRequest(req); 32 | 33 | try { 34 | const events = await Event.find({ userId: user._id }); 35 | res.json(events); 36 | } catch (error) { 37 | res.status(500).json({ message: error.message }); 38 | } 39 | }); 40 | 41 | // Delete an event 42 | eventRoutes.delete('/delete/event/:id', authenticationToken, async (req, res) => { 43 | try { 44 | const event = await Event.findByIdAndDelete(req.params.id); 45 | if (!event) { 46 | return res.status(404).json({ message: 'Event not found' }); 47 | } 48 | res.json({ message: 'Event deleted' }); 49 | } catch (error) { 50 | res.status(500).json({ message: error.message }); 51 | } 52 | }); 53 | 54 | export default eventRoutes; 55 | -------------------------------------------------------------------------------- /backend/Routes/noteRoutes.js: -------------------------------------------------------------------------------- 1 | import multer from "multer"; 2 | import { Router } from "express"; 3 | import { storage } from "../utils/multer.js"; 4 | import { HTTP_STATUS, MESSAGES, ERROR_MESSAGES } from "../utils/const.js"; 5 | 6 | import { authenticationToken } from "../middlewares/user.middlewares.js"; 7 | import { 8 | addNoteController, 9 | archiveNoteController, 10 | bulkUpdateNotePinnedController, 11 | deleteMultipleNotesController, 12 | deleteNoteByIdcontroller, 13 | editNoteByIdController, 14 | getAllNotesController, 15 | getArchiveNotesController, 16 | searchNotesController, 17 | updateNotePinnedController, 18 | unArchiveController, 19 | updateNotesBackgroundController, 20 | viewNotesController, 21 | restoreDeletedNotesController 22 | } from "../controllers/noteControllers.js"; 23 | import noteModel from "../models/noteModel.js"; 24 | import mongoose from "mongoose"; 25 | 26 | const noteRoutes = Router(); 27 | 28 | // const upload = multer({ storage: storage }); 29 | //upload multiple attachments files 30 | const uploadMultiple = multer({ storage: storage }).array("attachments", 10); 31 | 32 | noteRoutes.post( 33 | "/add-note", 34 | authenticationToken, 35 | uploadMultiple, 36 | addNoteController 37 | ); 38 | 39 | 40 | noteRoutes.get("/view-note/:noteId", viewNotesController); 41 | 42 | 43 | // Configure multer to not accept any files 44 | const upload_note = multer().none(); // This allows only non-file data 45 | 46 | noteRoutes.put( 47 | "/edit-note/:noteId", 48 | authenticationToken, // Ensure this middleware runs first 49 | upload_note, // Use the updated multer configuration 50 | editNoteByIdController // Your controller function 51 | ); 52 | 53 | noteRoutes.put("/update-notes-background", authenticationToken,updateNotesBackgroundController); 54 | 55 | noteRoutes.get("/get-all-notes", authenticationToken, getAllNotesController); 56 | 57 | noteRoutes.get("/get-archived-notes", authenticationToken,getArchiveNotesController); 58 | 59 | noteRoutes.delete("/delete-note/:noteId", authenticationToken, deleteNoteByIdcontroller); 60 | 61 | noteRoutes.delete("/delete-multiple-notes", authenticationToken, deleteMultipleNotesController); 62 | 63 | noteRoutes.put( 64 | "/update-note-pinned/:noteId", 65 | authenticationToken, 66 | updateNotePinnedController 67 | ); 68 | 69 | noteRoutes.put('/bulk-update-notes-pinned', bulkUpdateNotePinnedController); 70 | 71 | noteRoutes.put('/archive-notes',archiveNoteController); 72 | noteRoutes.put('/un-archive-notes',unArchiveController); 73 | noteRoutes.get("/search-notes/", authenticationToken, searchNotesController); 74 | noteRoutes.put("/undo-delete-notes", authenticationToken, async (req, res) => { 75 | try { 76 | const { noteIds } = req.body; 77 | // Change this line - req.user might be structured differently 78 | const { user } = req.user; // or however your user ID is structured 79 | console.log("noteIds:", noteIds); 80 | if (!noteIds || !Array.isArray(noteIds) || noteIds.length === 0) { 81 | return res.status(HTTP_STATUS.BAD_REQUEST).json({ 82 | error: true, 83 | message: ERROR_MESSAGES.PROVIDE_FIELD_TO_UPDATE, 84 | }); 85 | } 86 | 87 | // Restore notes by setting `deleted` back to false 88 | const result = await noteModel.updateMany( 89 | { _id: { $in: noteIds }, userId: user._id }, 90 | { $set: { deleted: false, deletedAt: null } } 91 | ) 92 | 93 | console.log("Update result:", result); 94 | 95 | if (result.modifiedCount === 0) { 96 | return res.status(HTTP_STATUS.NOT_FOUND).json({ 97 | error: true, 98 | message: ERROR_MESSAGES.NOTES_NOT_FOUND, 99 | }); 100 | } 101 | 102 | return res.json({ 103 | error: false, 104 | message: MESSAGES.NOTES_RESTORED_SUCCESSFULLY, 105 | modifiedCount: result.modifiedCount, 106 | }); 107 | } catch (error) { 108 | console.error("Error restoring notes: ", error); 109 | return res 110 | .status(HTTP_STATUS.INTERNAL_SERVER_ERROR) 111 | .json({ error: true, message: ERROR_MESSAGES.INTERNAL_SERVER_ERROR }); 112 | } 113 | }); 114 | 115 | 116 | export default noteRoutes; 117 | -------------------------------------------------------------------------------- /backend/Routes/uploads/profilePhoto-1729164520517-905517046.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/backend/Routes/uploads/profilePhoto-1729164520517-905517046.jpg -------------------------------------------------------------------------------- /backend/Routes/uploads/profilePhoto-1729164538736-40559474.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/backend/Routes/uploads/profilePhoto-1729164538736-40559474.jpg -------------------------------------------------------------------------------- /backend/Routes/uploads/profilePhoto-1729164567074-811265604.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/backend/Routes/uploads/profilePhoto-1729164567074-811265604.jpg -------------------------------------------------------------------------------- /backend/Routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import multer from "multer"; 3 | import { storage } from "../utils/multer.js"; 4 | 5 | import { 6 | contactUsMailController, 7 | createAccountController, 8 | deleteUserController, 9 | feedbackSubmitController, 10 | getCurrentAccountController, 11 | googleAuthController, 12 | loginAccountController, 13 | updateEmailController, 14 | updateFullNameController, 15 | updatePhoneController, 16 | updateProfilePhotoController, 17 | verifyAccountController, 18 | } from "../controllers/user.controllers.js"; 19 | 20 | import { 21 | authenticationToken, 22 | createAccountMiddleware, 23 | } from "../middlewares/user.middlewares.js"; 24 | 25 | const upload = multer({ storage: storage }); 26 | //upload multiple attachments files 27 | // const uploadMultiple = multer({ storage: storage }).array("attachments", 10); 28 | 29 | const userRoutes = Router(); 30 | 31 | userRoutes.post( 32 | "/create-account", 33 | createAccountMiddleware, 34 | createAccountController 35 | ); 36 | 37 | userRoutes.get("/verify/:token", verifyAccountController); 38 | 39 | // Login 40 | userRoutes.post("/login", loginAccountController); 41 | 42 | userRoutes.get("/get-user", authenticationToken, getCurrentAccountController); 43 | 44 | userRoutes.delete("/delete-user", authenticationToken, deleteUserController); 45 | 46 | userRoutes.put( 47 | "/update-fullName", 48 | authenticationToken, 49 | updateFullNameController 50 | ); 51 | 52 | userRoutes.put("/update-email", authenticationToken, updateEmailController); 53 | 54 | userRoutes.put("/update-phone", updatePhoneController); 55 | 56 | userRoutes.put( 57 | "/update-profile-photo", 58 | upload.single("profilePhoto"), 59 | updateProfilePhotoController 60 | ); 61 | 62 | userRoutes.post("/google-auth", googleAuthController); 63 | 64 | userRoutes.post("/submit", feedbackSubmitController); 65 | 66 | userRoutes.post("/contact", contactUsMailController); 67 | 68 | export default userRoutes; 69 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import mongoose from "mongoose"; 3 | import dotenv from "dotenv"; 4 | import path from "path"; 5 | import { fileURLToPath } from "url"; 6 | import cors from "cors"; 7 | import userRoutes from "./Routes/user.routes.js"; 8 | import noteRoutes from "./Routes/noteRoutes.js"; 9 | import eventRoutes from "./Routes/eventRoutes.js"; 10 | 11 | 12 | // Defined __filename and __dirname 13 | const __filename = fileURLToPath(import.meta.url); 14 | const __dirname = path.dirname(__filename); 15 | 16 | // const Router = require("./Routes/Router"); 17 | 18 | const app = express(); 19 | 20 | app.use(express.json()); 21 | 22 | // Determine which .env file to use based on the NODE_ENV 23 | const envPath = 24 | process.env.NODE_ENV === "production" ? ".env.production" : ".env"; 25 | dotenv.config({ path: path.resolve(__dirname, envPath) }); 26 | 27 | const { MONGO_URI } = process.env; 28 | console.log(MONGO_URI); 29 | 30 | // Log the MongoDB URI to verify it's loaded 31 | console.log("MongoDB URI:", MONGO_URI); 32 | 33 | // Check if MONGO_URI is defined 34 | if (!MONGO_URI) { 35 | console.error("MONGO_URI is not defined in environment variables."); 36 | process.exit(1); // Exit the application if MONGO_URI is not set 37 | } 38 | 39 | // Use cors middleware before defining any routes 40 | const allowedOrigins = 41 | process.env.NODE_ENV === "production" 42 | ? ["https://scribbie-notes.vercel.app"] 43 | : ["http://localhost:5173"]; 44 | 45 | app.use( 46 | cors({ 47 | origin: allowedOrigins, 48 | credentials: true, // access-control-allow-credentials:true 49 | methods: "GET,PUT,POST,DELETE", 50 | optionSuccessStatus: 200, 51 | }) 52 | ); 53 | 54 | // Connect to MongoDB 55 | (async function () { 56 | try { 57 | await mongoose.connect(MONGO_URI) 58 | console.log("MongoDB connected") 59 | } catch (error) { 60 | console.error("MongoDB connection error:", error); 61 | } 62 | })(); 63 | 64 | //new better and structured routes 65 | app.use(userRoutes); 66 | app.use(noteRoutes); 67 | app.use(eventRoutes); 68 | 69 | const PORT = process.env.PORT || 5000; 70 | app.listen(PORT, () => { 71 | console.log(`Server is running on http://localhost:${PORT}`); 72 | }); 73 | 74 | 75 | export default app; -------------------------------------------------------------------------------- /backend/mail/contactUsMailSender.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer' 2 | import asyncHandler from 'express-async-handler' 3 | import dotenv from 'dotenv' 4 | dotenv.config(); 5 | const transporter = nodemailer.createTransport({ 6 | host: process.env.BREVO_HOST, 7 | port: process.env.BREVO_PORT, 8 | secure: false, 9 | auth: { 10 | user: process.env.BREVO_USER, 11 | pass: process.env.BREVO_PASSWORD, 12 | }, 13 | }); 14 | 15 | const contactSendMail = asyncHandler(async (email, name, html) => { 16 | await transporter.sendMail({ 17 | from: '"Notes App" ', 18 | to: email, 19 | name: name, 20 | html: html, 21 | }); 22 | }); 23 | 24 | export default contactSendMail; -------------------------------------------------------------------------------- /backend/mail/forgotPAsswordOtpMail.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const nodemailer = require("nodemailer"); 3 | 4 | // Create a Nodemailer transporter using SMTP 5 | const transporter = nodemailer.createTransport({ 6 | service: "gmail", // or your preferred email service 7 | auth: { 8 | user: process.env.BREVO_USER, 9 | pass: process.env.BREVO_PASS, 10 | }, 11 | }); 12 | 13 | exports.sendVerificationMail = async(email, verificationCode) => { 14 | const emailText = ` 15 | Dear Customer, 16 | 17 | Please use this verification code for resetting your password. Here's your code': 18 | 19 | Code: ${verificationCode} 20 | 21 | Thank you for choosing our service. We are happy to help you. 22 | 23 | Best regards, 24 | `; 25 | console.log("hii"); 26 | 27 | try { 28 | await transporter.sendMail({ 29 | from: '"Notes App" ', 30 | to: email, 31 | subject: "Password Reset Verification Code", 32 | text: emailText, 33 | }); 34 | 35 | console.log("hlo"); 36 | 37 | 38 | } catch (error) { 39 | console.log(`Failed to send verification email: ${error.message}`); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /backend/mail/sendMail.js: -------------------------------------------------------------------------------- 1 | import nodemailer from 'nodemailer'; 2 | import asyncHandler from 'express-async-handler'; 3 | import dotenv from 'dotenv' 4 | dotenv.config(); 5 | const transporter = nodemailer.createTransport({ 6 | host: process.env.BREVO_HOST, 7 | port: process.env.BREVO_PORT, 8 | secure: false, 9 | auth: { 10 | user: process.env.BREVO_USER, 11 | pass: process.env.BREVO_PASSWORD, 12 | }, 13 | }); 14 | 15 | const sendMail = asyncHandler(async (email, url) => { 16 | await transporter.sendMail({ 17 | from: '"Notes App" ', 18 | to: email, 19 | subject: "Verification Link For Notes Account", 20 | text: "Click the link below to verify your account", 21 | html: url, 22 | }); 23 | }); 24 | 25 | export default sendMail; -------------------------------------------------------------------------------- /backend/middlewares/user.middlewares.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { HTTP_STATUS, ERROR_MESSAGES } from "../utils/const.js"; 3 | const { ACCESS_TOKEN_SECRET, GOOGLE_API_TOKEN } = process.env; 4 | 5 | 6 | const createAccountMiddleware = async (req, res, next) => { 7 | const { fullName, email, password } = req.body; 8 | 9 | // fullname validations 10 | if (!fullName || fullName.trim() === "") { 11 | return res 12 | .status(HTTP_STATUS.BAD_REQUEST) 13 | .json({ error: true, message: ERROR_MESSAGES.NAME_REQUIRED }); 14 | } 15 | const nameRegex = /^[A-Za-z]+(?: [A-Za-z]+)*$/; 16 | if (!nameRegex.test(fullName)) { 17 | return res 18 | .status(HTTP_STATUS.BAD_REQUEST) 19 | .json({ error: true, message: ERROR_MESSAGES.INVALID_NAME_FORMAT }); 20 | } 21 | 22 | // email validations 23 | if (!email || email.trim() === "") { 24 | return res 25 | .status(HTTP_STATUS.BAD_REQUEST) 26 | .json({ error: true, message: ERROR_MESSAGES.EMAIL_REQUIRED }); 27 | } 28 | const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 29 | if (!emailRegex.test(email)) { 30 | return res 31 | .status(HTTP_STATUS.BAD_REQUEST) 32 | .json({ error: true, message: ERROR_MESSAGES.INVALID_EMAIL_FORMAT }); 33 | } 34 | 35 | // password validaitons 36 | if (!password || password.trim() === "") { 37 | return res 38 | .status(HTTP_STATUS.BAD_REQUEST) 39 | .json({ error: true, message: ERROR_MESSAGES.PASSWORD_REQUIRED }); 40 | } 41 | if (!/[A-Z]/.test(password)) { 42 | return res.status(HTTP_STATUS.BAD_REQUEST).json({ 43 | error: true, 44 | message: ERROR_MESSAGES.PASSWORD_UPPERCASE_REQUIRED, 45 | }); 46 | } 47 | if (!/[a-z]/.test(password)) { 48 | return res.status(HTTP_STATUS.BAD_REQUEST).json({ 49 | error: true, 50 | message: ERROR_MESSAGES.PASSWORD_LOWERCASE_REQUIRED, 51 | }); 52 | } 53 | if (!/[!"#$%&'()*+,-.:;<=>?@[\]^_`{|}~]/.test(password)) { 54 | return res.status(HTTP_STATUS.BAD_REQUEST).json({ 55 | error: true, 56 | message: ERROR_MESSAGES.PASSWORD_SPECIAL_CHAR_REQUIRED, 57 | }); 58 | } 59 | if (!(password.length >= 8)) { 60 | return res 61 | .status(HTTP_STATUS.BAD_REQUEST) 62 | .json({ error: true, message: ERROR_MESSAGES.PASSWORD_MIN_LENGTH }); 63 | } 64 | 65 | next(); 66 | }; 67 | 68 | const authenticationToken = (req, res, next) => { 69 | // console.log(req.headers) 70 | const token = req.headers["authorization"].split(" ")[1]; 71 | // console.log("Authorization header:", token); // Log the token for debugging 72 | 73 | if (!token) { 74 | return res.status(403).json({ message: "No token provided." }); 75 | } 76 | 77 | jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => { 78 | if (err) { 79 | console.error("Token verification error:", err); // Log the error 80 | return res.status(403).json({ message: "Token verification failed." }); 81 | } 82 | req.user = user; // Attach user data to the request object 83 | next(); // Proceed to the next middleware/route handler 84 | }); 85 | }; 86 | 87 | export { createAccountMiddleware, authenticationToken }; 88 | -------------------------------------------------------------------------------- /backend/models/contactModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const contactSchema = new mongoose.Schema({ 4 | first_name: { 5 | type: String, 6 | required: true, 7 | }, 8 | last_name: { 9 | type: String, 10 | required: false, // Make this optional if you don't always collect it 11 | }, 12 | user_email: { 13 | type: String, 14 | required: true, 15 | match: /.+\@.+\..+/ // Basic email format validation 16 | }, 17 | message: { 18 | type: String, 19 | required: true, 20 | minlength: 1, // Minimum length of message 21 | }, 22 | }, { 23 | timestamps: true, // Automatically adds createdAt and updatedAt fields 24 | }); 25 | 26 | const Contact = mongoose.model('Contact', contactSchema); 27 | 28 | module.exports = Contact; 29 | -------------------------------------------------------------------------------- /backend/models/eventModel.js: -------------------------------------------------------------------------------- 1 | // models/Event.js 2 | import mongoose from "mongoose"; 3 | 4 | const eventSchema = new mongoose.Schema({ 5 | date: { 6 | type: String, 7 | required: true, 8 | }, 9 | title: { 10 | type: String, 11 | required: true, 12 | }, 13 | color: { 14 | type: String, 15 | required: true, 16 | }, 17 | description: { 18 | type: String, 19 | required: false, 20 | }, 21 | userId: { type: String, required: true }, 22 | }); 23 | 24 | const Event = mongoose.model('Event', eventSchema); 25 | export default Event; 26 | -------------------------------------------------------------------------------- /backend/models/feedbackModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const feedbackSchema = new Schema({ 6 | name: { type: String, required: true }, 7 | email: { type: String, required: true }, 8 | feedback: { type: String, required: true }, 9 | rating:{type:Number,required:true}, 10 | createdOn: { type: Date, default: Date.now } 11 | }); 12 | 13 | const Feedback = mongoose.model("Feedback", feedbackSchema); 14 | export default Feedback; -------------------------------------------------------------------------------- /backend/models/noteModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const Schema = mongoose.Schema; 4 | 5 | const noteSchema = new Schema({ 6 | title: { type: String, required: true }, 7 | content: { type: String, required: true }, 8 | tags: { type: [String], default: [] }, // Ensure this is an array 9 | attachments:{type:[String],default:[]}, 10 | isPinned: { type: Boolean, required: false }, 11 | isArchived:{type:Boolean , default:false}, 12 | deleted: { 13 | type: Boolean, 14 | default: false, // Default is false, meaning the note is not deleted 15 | }, 16 | userId: { type: String, required: true }, 17 | createdOn: { type: Date, default: Date.now }, 18 | background: { type: String }, 19 | deletedAt: Date, 20 | 21 | }); 22 | 23 | 24 | export default mongoose.model("Note", noteSchema); 25 | -------------------------------------------------------------------------------- /backend/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import bcrypt from "bcrypt"; // Import bcrypt 3 | 4 | const Schema = mongoose.Schema; 5 | 6 | const userSchema = new Schema({ 7 | fullName: { type: String }, 8 | email: { type: String }, 9 | password: { type: String }, 10 | phone: { type: String }, 11 | createdOn: { type: Date, default: new Date().getTime() }, 12 | profilePhoto: { type: String }, 13 | verificationCode: {type: String, default: ""}, 14 | isEmailVerified: { type: Boolean, default: false }, 15 | 16 | }); 17 | // hashing the password before saving it to the database by using PRE 18 | userSchema.pre("save", async function (next) { 19 | if (!this.isModified("password")) return next(); 20 | //this will hash the password only the password field is bieng modified 21 | this.password = await bcrypt.hash(this.password, 10); 22 | next(); 23 | }); 24 | //creating a our own method for validating the password entered by the user 25 | userSchema.methods.checkPassword = async function (password) { 26 | return await bcrypt.compare(password, this.password); 27 | }; 28 | 29 | export default mongoose.model("User", userSchema); 30 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notes-app-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon index.js" 9 | }, 10 | "type": "module", 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@react-oauth/google": "^0.12.1", 15 | "axios": "^1.7.2", 16 | "bcrypt": "^5.1.1", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.4.5", 19 | "express": "^4.21.0", 20 | "express-async-handler": "^1.2.0", 21 | "express-rate-limit": "^7.4.1", 22 | "fs": "^0.0.1-security", 23 | "google-auth-library": "^9.11.0", 24 | "jsonwebtoken": "^9.0.2", 25 | "moment": "^2.30.1", 26 | "mongodb": "^6.9.0", 27 | "mongoose": "^8.4.4", 28 | "multer": "^1.4.5-lts.1", 29 | "nodemailer": "^6.9.15", 30 | "notes-app-backend": "file:" 31 | }, 32 | "devDependencies": { 33 | "nodemon": "^3.1.7" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/uploads/profilePhoto-1721713869259-990478442.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/backend/uploads/profilePhoto-1721713869259-990478442.png -------------------------------------------------------------------------------- /backend/uploads/profilePhoto-1721713968989-129215121.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/backend/uploads/profilePhoto-1721713968989-129215121.jpeg -------------------------------------------------------------------------------- /backend/uploads/profilePhoto-1721714084138-572334207.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/backend/uploads/profilePhoto-1721714084138-572334207.jpg -------------------------------------------------------------------------------- /backend/utilities.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const dotenv = require('dotenv') 3 | dotenv.config() 4 | const { ACCESS_TOKEN_SECRET } = process.env; 5 | 6 | 7 | const authenticationToken = (req, res, next) => { 8 | const token = req.headers["authorization"]; 9 | if (!token) return res.sendStatus(403); 10 | 11 | jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => { 12 | if (err) return res.sendStatus(403); 13 | req.user = user; 14 | next(); 15 | }); 16 | }; 17 | 18 | 19 | module.exports = authenticationToken ; 20 | -------------------------------------------------------------------------------- /backend/utils/const.js: -------------------------------------------------------------------------------- 1 | // HTTP Status Codes 2 | const HTTP_STATUS = { 3 | OK: 200, 4 | CREATED: 201, 5 | BAD_REQUEST: 400, 6 | UNAUTHORIZED: 401, 7 | NOT_FOUND: 404, 8 | INTERNAL_SERVER_ERROR: 500, 9 | }; 10 | 11 | // Success Messages 12 | const MESSAGES = { 13 | USER_REGISTERED_SUCCESSFULLY: "Registration Successful", 14 | LOGIN_SUCCESSFUL: "Login Successful", 15 | NOTE_ADDED_SUCCESSFULLY: "Note added successfully", 16 | NOTE_UPDATED_SUCCESSFULLY: "Note updated successfully", 17 | NOTES_FETCHED_SUCCESSFULLY: "Notes fetched successfully", 18 | NOTE_DELETED_SUCCESSFULLY: "Note deleted successfully", 19 | EMAIL_UPDATED_SUCCESSFULLY: "Email updated successfully", 20 | FULLNAME_UPDATED_SUCCESSFULLY: "Name updated successfully", 21 | PHONE_UPDATED_SUCCESSFULLY: "Phone number updated successfully", 22 | PROFILE_PHOTO_UPDATED_SUCCESSFULLY: "Profile photo updated successfully", 23 | GOOGLE_AUTH_SUCCESSFUL: "Google authentication successful", 24 | FEEDBACK_SUBMITTED_SUCCESSFULLY: "Feedback submitted successfully", 25 | }; 26 | 27 | // Error Messages 28 | const ERROR_MESSAGES = { 29 | // User Registration Errors 30 | NAME_REQUIRED: "Name is required", 31 | INVALID_NAME_FORMAT: "Invalid Name format", 32 | EMAIL_REQUIRED: "Email is required", 33 | INVALID_EMAIL_FORMAT: "Invalid Email format", 34 | PASSWORD_REQUIRED: "Password is required", 35 | PASSWORD_UPPERCASE_REQUIRED: 36 | "Password must include at least one Uppercase letter", 37 | PASSWORD_LOWERCASE_REQUIRED: 38 | "Password must include at least one Lowercase letter", 39 | PASSWORD_SPECIAL_CHAR_REQUIRED: 40 | "Password must include at least one special character", 41 | PASSWORD_MIN_LENGTH: "Min password length should be 8", 42 | USER_ALREADY_EXISTS: "User already exists", 43 | EMAIL_NOT_VERIFIED: "Email not verified, Please verify your email.", 44 | 45 | // Authentication Errors 46 | EMAIL_PASSWORD_REQUIRED: "Email and Password are required", 47 | INVALID_CREDENTIALS: "Invalid Credentials", 48 | USER_NOT_AUTHENTICATED: "User not authenticated", 49 | 50 | // Note-related Errors 51 | TITLE_CONTENT_REQUIRED: "Title and Content are required", 52 | PROVIDE_FIELD_TO_UPDATE: "Please provide at least one field to update", 53 | NOTE_NOT_FOUND: "Note not found", 54 | PROVIDE_IS_PINNED_FIELD: "Please provide isPinned field", 55 | PROVIDE_SEARCH_QUERY: "Please provide at least one field to search", 56 | 57 | // User-related Errors 58 | USER_NOT_FOUND: "User not found", 59 | 60 | // Google OAuth Errors 61 | INVALID_GOOGLE_TOKEN: "Invalid Google token", 62 | 63 | // Feedback Errors 64 | FAILED_TO_SUBMIT_FEEDBACK: "Failed to submit feedback", 65 | 66 | // General Errors 67 | INTERNAL_SERVER_ERROR: "Internal server error", 68 | }; 69 | 70 | export { HTTP_STATUS, MESSAGES, ERROR_MESSAGES }; 71 | -------------------------------------------------------------------------------- /backend/utils/multer.js: -------------------------------------------------------------------------------- 1 | //util 2 | import fs from "fs"; 3 | import multer from "multer"; 4 | import path from "path"; 5 | 6 | export const storage = multer.diskStorage({ 7 | destination: (req, file, cb) => { 8 | const uploadPath = path.join(__dirname, "uploads"); 9 | if (!fs.existsSync(uploadPath)) { 10 | fs.mkdirSync(uploadPath); 11 | } 12 | cb(null, uploadPath); 13 | }, 14 | filename: (req, file, cb) => { 15 | const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); 16 | cb( 17 | null, 18 | file.fieldname + "-" + uniqueSuffix + path.extname(file.originalname) 19 | ); 20 | }, 21 | }); -------------------------------------------------------------------------------- /backend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "*.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/", 12 | "dest": "/" 13 | }, 14 | { 15 | "src": "/landing", 16 | "dest": "/" 17 | }, 18 | { 19 | "src": "/dashboard", 20 | "dest": "/" 21 | }, 22 | { 23 | "src": "/login", 24 | "dest": "/" 25 | }, 26 | { 27 | "src": "/signup", 28 | "dest": "/" 29 | }, 30 | { 31 | "src": "/about", 32 | "dest": "/" 33 | }, 34 | { 35 | "src": "/my-profile", 36 | "dest": "/" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | # Frontend Environment Variables 2 | 3 | # Base URL of the backend server 4 | VITE_BACKEND_URL=http://localhost:5000 5 | 6 | # API key for Google services (if used in frontend) 7 | VITE_REACT_APP_GOOGLE_API_TOKEN=114458585610-q8v3k7sdfbu55j876cj11km7h12tpj02.apps.googleusercontent.com -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | # Frontend Environment Variables 2 | 3 | # Base URL of the backend server 4 | VITE_BACKEND_URL=http://localhost:5000 5 | 6 | # API key for Google services (if used in frontend) 7 | VITE_GOOGLE_CLIENT=your_client_id 8 | VITE_GOOGLE_API_TOKEN=your_api_token 9 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | Scribbie 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notes-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host 0.0.0.0", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.13.3", 14 | "@emotion/styled": "^11.13.0", 15 | "@mui/material": "^6.1.6", 16 | "@react-oauth/google": "^0.12.1", 17 | "axios": "^1.7.2", 18 | "cookies-js": "^1.2.3", 19 | "cors": "^2.8.5", 20 | "dotenv": "^16.4.5", 21 | "framer-motion": "^11.11.6", 22 | "gapi-script": "^1.2.0", 23 | "gsap": "^3.12.5", 24 | "http-proxy-middleware": "^3.0.0", 25 | "js-cookie": "^3.0.5", 26 | "moment": "^2.30.1", 27 | "notes-app": "file:", 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0", 30 | "react-hot-toast": "^2.4.1", 31 | "react-icons": "^5.3.0", 32 | "react-modal": "^3.16.1", 33 | "react-quill": "^2.0.0", 34 | "react-router-dom": "^6.26.2", 35 | "react-scroll": "^1.9.0", 36 | "react-simple-scroll-up": "^0.2.3" 37 | }, 38 | "devDependencies": { 39 | "@types/react": "^18.2.66", 40 | "@types/react-dom": "^18.2.22", 41 | "@vitejs/plugin-react": "^4.2.1", 42 | "autoprefixer": "^10.4.19", 43 | "eslint": "^8.57.0", 44 | "eslint-plugin-react": "^7.34.1", 45 | "eslint-plugin-react-hooks": "^4.6.0", 46 | "eslint-plugin-react-refresh": "^0.4.6", 47 | "postcss": "^8.4.38", 48 | "tailwindcss": "^3.4.3", 49 | "vite": "^5.2.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/testimonial-section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/public/testimonial-section.png -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Poppins', sans-serif; 3 | } 4 | 5 | .note-content .ql-editor { 6 | font-family: 'Poppins', sans-serif; 7 | } -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { 3 | BrowserRouter as Router, 4 | Routes, 5 | Route, 6 | Navigate, 7 | useLocation, 8 | } from "react-router-dom"; 9 | import Home from "./pages/Home/Home"; 10 | import Signup from "./pages/Signup/Signup"; 11 | import Login from "./pages/Login/Login"; 12 | import Hero from "./components/Hero/Hero"; 13 | import About from "./components/About/About"; 14 | import Loading from "./components/Loading"; 15 | import ProfilePage from "./pages/ProfilePage/ProfilePage"; 16 | import Testimonial from "./components/Testimonial"; 17 | import Pricing from "./components/Pricing"; 18 | import Footer from "./components/sticky_footer/Footer"; 19 | import Contact from "./components/Contact/Contact"; 20 | import Contributors from "./components/Contributors/Contributors"; 21 | import ArchivedNotes from "./components/ArchivedNotes/ArchivedNotes"; 22 | import Preloader from "./components/Preloader"; 23 | import VerifyEmail from "./pages/ForgotPassword/VerifyEmail"; 24 | import VerifyOtp from "./pages/ForgotPassword/VerifyOtp"; 25 | import NewPassword from "./pages/ForgotPassword/NewPassword"; 26 | import Calendar from "./components/Calendar/Calendar"; 27 | import { ScrollToTop } from "react-simple-scroll-up"; 28 | 29 | // currently this component is hide 30 | // import Navbar from './components/Navbar'; 31 | // import ProtectedRoute from './utils/ProtectedRoute'; 32 | // import ErrorPage from './components/ErrorPage'; 33 | // import Footer from './components/Footer'; 34 | 35 | const App = () => { 36 | const [loading, setLoading] = useState(false); 37 | const [user, setUser] = useState(null); 38 | const location = useLocation(); 39 | 40 | useEffect(() => { 41 | const handleRouteChange = () => { 42 | setLoading(true); 43 | setTimeout(() => { 44 | setLoading(false); 45 | }, 500); 46 | }; 47 | handleRouteChange(); 48 | }, [location]); 49 | 50 | useEffect(() => { 51 | const storedUser = localStorage.getItem("user"); 52 | if (storedUser) { 53 | try { 54 | setUser(JSON.parse(storedUser)); 55 | } catch (e) { 56 | console.error("Error parsing stored user", e); 57 | } 58 | } 59 | }, []); 60 | 61 | return ( 62 |
63 | {/* */} 64 | {loading && } 65 | {user && location.pathname === "/" ? ( 66 | 67 | ) : ( 68 | <> 69 | 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | } /> 76 | } /> 77 | } /> 78 | } /> 79 | } /> 80 | } /> 81 | 82 | } /> 83 | } /> 84 | } /> 85 | } /> 86 | } /> 87 | {/* } /> */} 88 | 89 | {location.pathname !== "/dashboard" && ( 90 | 94 | 95 | 96 | } 97 | size={40} 98 | bgColor="#111827" 99 | strokeWidth={3} 100 | strokeFillColor="#6B7280" 101 | strokeEmptyColor="#CBCBCB" 102 | symbolColor="#fff" 103 | /> 104 | )} 105 | 106 | )} 107 |
108 | ); 109 | }; 110 | 111 | const AppWithRouter = () => ( 112 | 113 | 114 | 115 | ); 116 | 117 | export default AppWithRouter; -------------------------------------------------------------------------------- /frontend/src/assets/images/add-notes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/images/addPost.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/cross.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/hero1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/src/assets/images/hero1.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/hero2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/src/assets/images/hero2.png -------------------------------------------------------------------------------- /frontend/src/assets/images/hero3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/src/assets/images/hero3.png -------------------------------------------------------------------------------- /frontend/src/assets/images/logo/dark-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/src/assets/images/logo/dark-logo.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/logo/whiteLogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scribbie-Notes/notes-app/a5a6d6d0fb634a3b6605a00f37fad8895838df55/frontend/src/assets/images/logo/whiteLogo.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/no-data.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/assets/images/noFound.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Cards/NoteCard.jsx: -------------------------------------------------------------------------------- 1 | import ReactQuill from "react-quill"; 2 | import "react-quill/dist/quill.snow.css"; 3 | import PropTypes from "prop-types"; 4 | import moment from "moment"; 5 | import { 6 | MdCreate, 7 | MdDelete, 8 | MdOutlinePushPin, 9 | MdCheckBox, 10 | MdCheckBoxOutlineBlank, 11 | MdDownload, 12 | MdShare, // Import Share Icon 13 | } from "react-icons/md"; 14 | 15 | const getContrastColor = (background) => { 16 | const hex = background.replace("#", ""); 17 | const r = parseInt(hex.substr(0, 2), 16); 18 | const g = parseInt(hex.substr(2, 2), 16); 19 | const b = parseInt(hex.substr(4, 2), 16); 20 | const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; 21 | 22 | return luminance > 0.5 ? "black" : "white"; // Return black for light backgrounds, white for dark 23 | }; 24 | 25 | const truncateContent = (content) => { 26 | const words = content.split(" "); 27 | return words.length > 25 ? words.slice(0, 25).join(" ") + "..." : content; 28 | }; 29 | 30 | const NoteCard = ({ 31 | id, 32 | title, 33 | date, 34 | content, 35 | tags, 36 | isPinned, 37 | background, 38 | onEdit, 39 | onDelete, 40 | onPinNote, 41 | onClick, 42 | isSelected, 43 | onSelect, 44 | }) => { 45 | const textColor = getContrastColor(background); 46 | 47 | // Function to download the note 48 | const downloadNote = (content, title, date) => { 49 | const strippedContent = content.replace(/<[^>]+>/g, '').trim(); 50 | const formattedDate = moment(date).format("Do MMM YYYY"); 51 | const textToDownload = `Title: ${title}\nDate of Creation: ${formattedDate}\nContent: ${strippedContent}`; 52 | const blob = new Blob([textToDownload], { typze: "text/plain;charset=utf-8" }); 53 | const link = document.createElement("a"); 54 | link.href = URL.createObjectURL(blob); 55 | link.download = `${title}.txt`; 56 | link.click(); 57 | }; 58 | 59 | const apiBaseUrl = import.meta.env.VITE_BACKEND_URL; 60 | 61 | 62 | // Share the note by copying the link to the clipboard 63 | const shareNote = (id, title) => { 64 | const noteLink = `${apiBaseUrl}/view-note/${id}`; // Modify based on your app's route 65 | navigator.clipboard.writeText(noteLink).then(() => { 66 | alert(`Note link copied: ${noteLink}`); 67 | }); 68 | }; 69 | 70 | return ( 71 |
75 |
76 |
77 |
{title}
78 | 79 | {moment(date).format("Do MMM YYYY")} 80 | 81 |
82 | 83 |
84 | { 87 | e.stopPropagation(); 88 | onPinNote(); 89 | }} 90 | /> 91 |
92 | {isPinned ? "Unpin Note" : "Pin Note"} 93 |
94 |
95 |
96 | 97 |

110 | 111 |

112 |
113 | {tags.length > 0 && 114 | tags.map((tag, index) => ( 115 | 119 | {tag !== "" ? `#${tag}` : ""} 120 | 121 | ))} 122 |
123 | 124 |
125 |
126 | { 129 | e.stopPropagation(); 130 | onEdit(); 131 | }} 132 | /> 133 |
134 | {"Edit Note"} 135 |
136 |
137 | 138 |
139 | { 142 | e.stopPropagation(); 143 | onDelete(); 144 | }} 145 | /> 146 |
147 | {"Delete Note"} 148 |
149 |
150 | 151 | {/* Export Icon (Download Button) */} 152 |
153 | { 156 | e.stopPropagation(); 157 | downloadNote(content, title, date); 158 | }} 159 | /> 160 |
161 | {"Export Note"} 162 |
163 |
164 | 165 | {/* Share Icon */} 166 |
167 | { 170 | e.stopPropagation(); 171 | shareNote(id, title); 172 | }} 173 | /> 174 |
175 | {"Share Note"} 176 |
177 |
178 | 179 |
{ 181 | e.stopPropagation(); 182 | onSelect(id); 183 | }} 184 | > 185 | 186 | {isSelected ? ( 187 | 188 | ) : ( 189 | 190 | )} 191 | 192 |
193 |
194 |
195 |
196 | ); 197 | }; 198 | 199 | NoteCard.propTypes = { 200 | title: PropTypes.string.isRequired, 201 | date: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]).isRequired, 202 | content: PropTypes.string.isRequired, 203 | tags: PropTypes.arrayOf(PropTypes.string).isRequired, 204 | isPinned: PropTypes.bool, 205 | background: PropTypes.string, 206 | onEdit: PropTypes.func, 207 | onDelete: PropTypes.func, 208 | onPinNote: PropTypes.func, 209 | onClick: PropTypes.func, 210 | isSelected: PropTypes.bool, 211 | onSelect: PropTypes.func, 212 | }; 213 | 214 | NoteCard.defaultProps = { 215 | isPinned: false, 216 | background: "#ffffff", 217 | onEdit: () => { }, 218 | onDelete: () => { }, 219 | onPinNote: () => { }, 220 | onClick: () => { }, 221 | isSelected: false, 222 | onSelect: () => { }, 223 | }; 224 | 225 | export default NoteCard; 226 | -------------------------------------------------------------------------------- /frontend/src/components/Cards/ProfileInfo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import { getInitials } from "../../utils/helper"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { CiUser, CiCircleInfo, CiLogout } from "react-icons/ci"; 5 | import { MdOutlineArchive } from "react-icons/md"; 6 | import { FaRegTrashAlt } from "react-icons/fa"; 7 | 8 | const ProfileInfo = ({ userInfo, onLogout }) => { 9 | const [isDropdownOpen, setIsDropdownOpen] = useState(false); 10 | const [isModalOpen, setIsModalOpen] = useState(false); 11 | const navigate = useNavigate(); 12 | const dropdownRef = useRef(null); 13 | 14 | const toggleDropdown = () => { 15 | setIsDropdownOpen(!isDropdownOpen); 16 | }; 17 | 18 | // const handleMyProfile = () => { 19 | // navigate("/my-profile"); 20 | // setIsDropdownOpen(false); 21 | // }; 22 | 23 | const handleArchivedNotes = () => { 24 | navigate("/archived-notes"); 25 | setIsDropdownOpen(false) 26 | } 27 | 28 | const handleAbout = () => { 29 | navigate("/about"); 30 | setIsDropdownOpen(false); 31 | }; 32 | 33 | const handleLogout = () => { 34 | setIsDropdownOpen(false); 35 | setIsModalOpen(true); // Open the confirmation modal 36 | }; 37 | 38 | const confirmLogout = () => { 39 | onLogout(); 40 | setIsModalOpen(false); 41 | setIsDropdownOpen(false); 42 | }; 43 | 44 | const closeModal = () => { 45 | setIsModalOpen(false); 46 | }; 47 | 48 | const handleClickOutside = (event) => { 49 | if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 50 | setIsDropdownOpen(false); 51 | } 52 | }; 53 | 54 | useEffect(() => { 55 | if (isDropdownOpen) { 56 | document.addEventListener("click", handleClickOutside); 57 | } else { 58 | document.removeEventListener("click", handleClickOutside); 59 | } 60 | 61 | return () => { 62 | document.removeEventListener("click", handleClickOutside); 63 | }; 64 | }, [isDropdownOpen]); 65 | 66 | return ( 67 |
68 |
72 | {getInitials(userInfo?.fullName)} 73 |
74 | 75 | {isDropdownOpen && ( 76 |
77 | {/* */} 86 | {/* */} 95 | 104 | 113 |
114 | )} 115 | 116 | {/* Modal for logout confirmation */} 117 | {isModalOpen && ( 118 |
119 | 120 |
121 |

Confirm Logout

122 |

Are you sure you want to log out?

123 |
124 | 130 | 137 |
138 |
139 |
140 | )} 141 |
142 | ); 143 | }; 144 | 145 | export default ProfileInfo; 146 | -------------------------------------------------------------------------------- /frontend/src/components/CircularLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const CircularLoader = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default CircularLoader; 12 | -------------------------------------------------------------------------------- /frontend/src/components/Contact/Contact.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from "react"; 2 | import Navbar from "../Navbar"; 3 | import { MdFacebook } from "react-icons/md"; 4 | import { FaLinkedin } from "react-icons/fa"; 5 | import { IoLogoInstagram } from "react-icons/io5"; 6 | import { FaXTwitter } from "react-icons/fa6"; 7 | import axiosInstance from "../../utils/axiosInstance"; 8 | import toast from "react-hot-toast"; 9 | import Backdrop from "@mui/material/Backdrop"; 10 | import CircularProgress from "@mui/material/CircularProgress"; 11 | 12 | const Contact = () => { 13 | const [data, setData] = useState({ 14 | first_name: "", 15 | last_name: "", 16 | user_email: "", 17 | message: "", 18 | }); 19 | const [loading, setLoading] = useState(false); // State for loading indicator 20 | 21 | const changeHandler = (e) => { 22 | setData((prevData) => ({ 23 | ...prevData, 24 | [e.target.name]: e.target.value, 25 | })); 26 | }; 27 | 28 | const submitHandler = async (e) => { 29 | e.preventDefault(); 30 | console.log("FORM DATA----: ", data); 31 | 32 | if (!data.user_email || !data.first_name || !data.message) { 33 | toast.error("All Fields are required :)"); 34 | return; 35 | } 36 | 37 | setLoading(true); 38 | try { 39 | const response = await axiosInstance.post("/contact", data); 40 | 41 | if (response.data.error) { 42 | toast.error("Failed submission"); 43 | } else { 44 | toast.success("Will connect to you soon"); 45 | setData({ first_name: "", last_name: "", user_email: "", message: "" }); 46 | } 47 | } catch (error) { 48 | toast.error("Submission failed"); 49 | } finally { 50 | setLoading(false); // Hide loading indicator 51 | } 52 | }; 53 | 54 | useEffect(() => { 55 | window.scrollTo(0, 0); 56 | }, []); 57 | 58 | const form = useRef(); 59 | const user = JSON.parse(localStorage.getItem("user")); 60 | 61 | return ( 62 |
63 | 64 |
65 |
66 |

Get in touch

67 |
68 |

Visit us

69 |

Come say hello at our office HQ.

70 |

71 | 67 Wistoria Way Croydon South VIC 3136 AU 72 |

73 |
74 |
75 |

Chat to us

76 |

Our friendly team is here to help.

77 |

@Scribbie.com

78 |
79 |
80 |

Call us

81 |

Mon-Fri from 8am to 5pm

82 |

(+995) 555-55-55-55

83 |
84 |
85 |

Social media

86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 |
101 |
102 | 103 |
104 |
109 |
110 |
111 | 112 | 120 |
121 |
122 | 123 | 131 |
132 |
133 |
134 | 135 | 143 |
144 |
145 | 146 |