├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── README.md
├── database .js
├── frontend
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
│ ├── App.css
│ ├── App.js
│ ├── Game
│ ├── App.js
│ ├── Board.js
│ ├── Info.js
│ ├── app.css
│ ├── board.css
│ ├── index.css
│ ├── index.js
│ └── info.css
│ ├── Hooks
│ └── useOnline.js
│ ├── components
│ ├── About.js
│ ├── AddNote.js
│ ├── Alert.js
│ ├── EditNote.js
│ ├── Login.js
│ ├── Navbar.js
│ ├── NoteItems.js
│ ├── Notes.js
│ ├── Shimmer.js
│ └── Signup.js
│ ├── context
│ └── notes
│ │ ├── NoteState.js
│ │ └── noteContext.js
│ ├── index.css
│ └── index.js
├── index.js
├── middleware
├── error.js
└── fetchuser.js
├── models
├── Note.js
└── User.js
├── package-lock.json
├── package.json
└── routes
├── auth.js
└── notes.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | node_modules
3 |
4 | .env
5 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | rajatsingh241303@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Hacktoberfest Contributor's Guide
2 |
3 | Welcome to Hacktoberfest! This is a beginner-friendly guide for contributors who want to participate in this annual celebration of open source.
4 |
5 | ## What is Hacktoberfest?
6 |
7 | Hacktoberfest is a month-long event that encourages people to contribute to open source projects. It's a great opportunity to learn and grow while making a positive impact on the open source community.
8 |
9 | ## How to Contribute
10 |
11 | Here are the steps to get started with your Hacktoberfest contributions:
12 |
13 | 1. **Find a Project**: Browse through open source projects on platforms like GitHub, GitLab, or Bitbucket. Look for projects that interest you and that have the "Hacktoberfest" label.
14 |
15 | 2. **Fork the Repository**: Click the "Fork" button on the project's GitHub page to create your own copy of the repository.
16 |
17 | 3. **Clone Your Fork**: Use `git clone` to copy your forked repository to your local machine.
18 |
19 | `git clone https://github.com/your-username/project-name.git`
20 |
21 | 4. **Create a Branch**: Create a new branch for your contributions.
22 |
23 |
24 | 5. **Make Changes**: Make your desired contributions to the project. This could include adding features, fixing bugs, improving documentation, or anything else the project needs.
25 |
26 | 6. **Commit Your Changes**: Use `git commit` to commit your changes to your local branch.
27 |
28 |
29 | 7. **Push Your Changes**: Push your branch with your changes to your GitHub repository.
30 |
31 |
32 | 8. **Create a Pull Request**: Go to the original project's repository on GitHub and click the "New Pull Request" button. Compare and create a pull request from your branch to the original project's `main` or `master` branch.
33 |
34 | 9. **Follow Up**: Keep an eye on your pull request for any feedback or changes requested by the maintainers. Be responsive to their comments.
35 |
36 | 10. **Celebrate**: Once your pull request is accepted and merged, congratulations! You've made a successful contribution to Hacktoberfest.
37 |
38 | ## Tips for Successful Contributions
39 |
40 | - Read and follow the project's contribution guidelines.
41 | - Start with issues labeled as "beginner-friendly" if you're new to open source.
42 | - Communicate with maintainers and the community if you have questions or need help.
43 | - Be respectful and professional in all interactions.
44 | - Don't forget to check the [Hacktoberfest website](https://hacktoberfest.digitalocean.com/) for official rules and prizes.
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
49 |
50 |
Please Wait
51 |
52 |
53 |
54 |
55 |
56 |
57 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | body{
2 | font-family: cursive;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
40 | .note-list {
41 | display: flex;
42 | flex-wrap: wrap;
43 | justify-content: space-between;
44 | gap: 16px; /* Adjust the gap between shimmer cards as needed */
45 | }
46 |
47 | .shimmer-card {
48 | width: 30%; /* Adjust the width of each shimmer card as needed */
49 | height: 200px; /* Adjust the height of each shimmer card as needed */
50 | background: linear-gradient(-90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
51 | background-size: 200% 100%;
52 | animation: shimmer 1.5s infinite linear;
53 | border-radius: 8px; /* Add rounded corners if desired */
54 | }
55 |
56 | @keyframes shimmer {
57 | 0% {
58 | background-position: 200% 0;
59 | }
60 | 100% {
61 | background-position: -200% 0;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { useState } from "react";
3 | import About from "./components/About";
4 | import AddNote from "./components/AddNote";
5 | import Notes from "./components/Notes";
6 | import Navbar from "./components/Navbar";
7 | // BrowserRouter as Router means that we are using the BrowserRouter component as Router.
8 | // For example: Switch as Rajat means that we use Switch as Rajat.
9 | import { BrowserRouter, Routes, Route } from "react-router-dom";
10 | import NoteState from "./context/notes/NoteState";
11 | import Alert from "./components/Alert";
12 | import SignUp from "./components/Signup";
13 | import Login from "./components/Login";
14 | import useOnline from "./Hooks/useOnline";
15 | import Appp from "./Game/App.js";
16 |
17 | function App() {
18 | // console.log(window.navigator);
19 |
20 | const [alert, setAlert] = useState(null);
21 |
22 | const showAlert = (msg, type) => {
23 | setAlert({ msg: msg, type: type });
24 | setTimeout(() => {
25 | setAlert(null);
26 | }, 1000);
27 | };
28 |
29 | const isOnline = useOnline();
30 |
31 | return (
32 | <>
33 |
34 |
35 |
36 |
37 |
38 | {!isOnline ? (
39 |
40 | ) : (
41 |
42 |
43 | }
46 | />
47 | } />
48 | }
51 | />
52 | }
55 | />
56 | }
59 | />
60 |
61 |
62 | )}
63 |
64 |
65 | >
66 | );
67 | }
68 |
69 | export default App;
70 |
--------------------------------------------------------------------------------
/frontend/src/Game/App.js:
--------------------------------------------------------------------------------
1 | // Importing the required components
2 | import Board from './Board';
3 | import Info from "./Info";
4 |
5 | // Importing the CSS File
6 | import "./app.css";
7 |
8 | // Importing the useState hook
9 | import { useState } from 'react';
10 |
11 | function App() {
12 |
13 | // Creating a reset state, which indicates whether
14 | // the game should be reset or not
15 | const [reset, setReset] = useState(false);
16 |
17 | // Creating a winner state, which indicates
18 | // the current winner
19 | const [winner, setWinner] = useState('');
20 |
21 | // Sets the reset property to true
22 | // which starts the chain
23 | // reaction of resetting the board
24 | const resetBoard = () => {
25 | setReset(true);
26 | }
27 |
28 | return (
29 |
30 | {/* Shrinks the popup when there is no winner */}
31 |
32 | {/* Display the current winner */}
33 |
{winner}
34 | {/* Button used to reset the board */}
35 |
resetBoard()}>
36 | Reset Board
37 |
38 |
39 | {/* Custom made board component comprising of
40 | the tic-tac-toe board */}
41 |
43 |
44 |
45 | );
46 | }
47 |
48 | export default App;
49 |
--------------------------------------------------------------------------------
/frontend/src/Game/Board.js:
--------------------------------------------------------------------------------
1 | // Importing the CSS for the board
2 | import "./board.css";
3 |
4 | // Importing the useState hook, useEffect hook and useRef hook
5 | import { useState, useEffect, useRef } from "react";
6 |
7 | const Board = ({ reset, setReset, winner, setWinner }) => {
8 |
9 | // Creating a turn state, which indicates the current turn
10 | const [turn, setTurn] = useState(0);
11 |
12 | // Creating a data state, which contains the
13 | // current picture of the board
14 | const [data, setData] = useState(['', '', '', '', '',
15 | '', '', '', ''])
16 |
17 | // Creating a reference for the board
18 | const boardRef = useRef(null);
19 |
20 | // Function to draw on the board
21 | const draw = (event, index) => {
22 | // Draws only if the position is not taken
23 | // and winner is not decided yet
24 | if (data[index - 1] === '' && winner === '') {
25 |
26 | // Draws X if it's player 1's turn else draws O
27 | const current = turn === 0 ? "X" : "O"
28 |
29 | // Updating the data state
30 | data[index - 1] = current;
31 |
32 | //Drawing on the board
33 | event.target.innerText = current;
34 |
35 | // Switching the turn
36 | setTurn(turn === 0 ? 1 : 0)
37 | }
38 | }
39 |
40 | // UseEffect hook used to reset the board whenever
41 | // a winner is decided
42 | useEffect(() => {
43 |
44 | // Clearing the data state
45 | setData(['', '', '', '', '', '', '', '', '']);
46 |
47 | // Getting all the children(cells) of the board
48 | const cells = boardRef.current.children
49 |
50 | // Clearing out the board
51 | for (let i = 0; i < 9; i++) {
52 | cells[i].innerText = '';
53 | }
54 |
55 | // Resetting the turn to player 0
56 | setTurn(0);
57 |
58 | // Resetting the winner
59 | setWinner('');
60 | setReset(false);
61 | }, [reset, setReset, setWinner])
62 |
63 |
64 | // useEffect hook used to check for a winner
65 | useEffect(() => {
66 |
67 | // Checks for the win condition in rows
68 | const checkRow = () => {
69 | let ans = false;
70 | for (let i = 0; i < 9; i += 3) {
71 | ans |= (data[i] === data[i + 1] &&
72 | data[i] === data[i + 2] &&
73 | data[i] !== '')
74 | }
75 | return ans;
76 | }
77 |
78 | // Checks for the win condition in cols
79 | const checkCol = () => {
80 | let ans = false;
81 | for (let i = 0; i < 3; i++) {
82 | ans |= (data[i] === data[i + 3] &&
83 | data[i] === data[i + 6] &&
84 | data[i] !== '')
85 | }
86 | return ans;
87 | }
88 |
89 | // Checks for the win condition in diagonals
90 | const checkDiagonal = () => {
91 | return ((data[0] === data[4] &&
92 | data[0] === data[8] && data[0] !== '') ||
93 | (data[2] === data[4] && data[2] === data[6] &&
94 | data[2] !== ''));
95 | }
96 |
97 | // Checks if at all a win condition is present
98 | const checkWin = () => {
99 | return (checkRow() || checkCol() || checkDiagonal());
100 | }
101 |
102 | // Checks for a tie
103 | const checkTie = () => {
104 | let count = 0;
105 | data.forEach((cell) => {
106 | if (cell !== '') {
107 | count++;
108 | }
109 | })
110 | return count === 9;
111 | }
112 |
113 | // Setting the winner in case of a win
114 | if (checkWin()) {
115 | setWinner(turn === 0 ? "Player 2 Wins!" :
116 | "Player 1 Wins!");
117 | } else if (checkTie()) {
118 |
119 | // Setting the winner to tie in case of a tie
120 | setWinner("It's a Tie!");
121 | }
122 |
123 | })
124 |
125 | return (
126 |
127 |
draw(e, 1)}>
129 |
draw(e, 2)}>
131 |
draw(e, 3)}>
133 |
draw(e, 4)}>
135 |
draw(e, 5)}>
137 |
draw(e, 6)}>
139 |
draw(e, 7)}>
141 |
draw(e, 8)}>
143 |
draw(e, 9)}>
145 |
146 | )
147 | }
148 |
149 | export default Board;
150 |
--------------------------------------------------------------------------------
/frontend/src/Game/Info.js:
--------------------------------------------------------------------------------
1 | // Importing the css for the info
2 | import "./info.css";
3 |
4 | const Info = () => {
5 | return (
6 |
7 |
Player 1: X
8 |
Player 2: O
9 |
10 | )
11 | }
12 |
13 | export default Info;
14 |
--------------------------------------------------------------------------------
/frontend/src/Game/app.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Bellefair&display=swap');
2 |
3 | .App {
4 | width: 100vw;
5 | height: 100vh;
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | flex-direction: column;
10 | gap: 5vh;
11 | backdrop-filter: 5px;
12 | background-color: rgba(0,0,0,.125);;
13 | }
14 |
15 | .winner {
16 | transition: all ease-in .3s;
17 | display: flex;
18 | opacity: 1;
19 | font-size: 1.5rem;
20 | font-weight: 600;
21 | gap: 1vh;
22 | flex-direction: column;
23 | justify-content: center;
24 | align-items: center;
25 | width: 20vw;
26 | position: absolute;
27 | top: 50%;
28 | left: 50%;
29 | transform: translate(-50%, -70%);
30 | background-color: rgba(195, 141, 158, 0.863);
31 | backdrop-filter: 5px;
32 | padding: .5rem;
33 | padding-bottom: 1rem;
34 | border-radius: 10%;
35 | }
36 |
37 | .winner-text {
38 | padding: .3em 1em .25em;
39 | font-weight: 600;
40 | font-size: 2.5rem;
41 | color: white;
42 | font-family: 'Bellefair', serif;
43 | position: relative;
44 | text-align: center;
45 | line-height: 1.3;
46 | }
47 |
48 | .shrink {
49 | transform: scale(.1);
50 | opacity: 0;
51 | }
52 |
53 | button {
54 | background-color: #111827;
55 | border: 1px solid transparent;
56 | border-radius: .75rem;
57 | box-sizing: border-box;
58 | color: #FFFFFF;
59 | cursor: pointer;
60 | flex: 0 0 auto;
61 | font-family: "Inter var";
62 | font-size: 1.125rem;
63 | font-weight: 600;
64 | line-height: 1.5rem;
65 | padding: .75rem 1.2rem;
66 | text-align: center;
67 | text-decoration: none #6B7280 solid;
68 | text-decoration-thickness: auto;
69 | transition-duration: .2s;
70 | transition-property: background-color, border-color,
71 | color, fill, stroke;
72 | transition-timing-function: cubic-bezier(.4, 0, 0.2, 1);
73 | user-select: none;
74 | -webkit-user-select: none;
75 | touch-action: manipulation;
76 | width: auto;
77 | }
78 |
79 | button:hover {
80 | background-color: #374151;
81 | }
82 |
83 | button:focus {
84 | box-shadow: none;
85 | outline: 2px solid transparent;
86 | outline-offset: 2px;
87 | }
88 |
89 | @media (min-width: 768px) {
90 | button {
91 | padding: .75rem 1.5rem;
92 | }
93 | }
94 |
95 | ;
96 |
--------------------------------------------------------------------------------
/frontend/src/Game/board.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --board-background: none;
3 | --border-color: #f6546a;
4 | --border-thickness: 5px;
5 | }
6 |
7 | .board {
8 | width: 30vw;
9 | height: 50%;
10 | background-color: var(--board-background);
11 | display: flex;
12 | align-items: flex-start;
13 | flex-direction: row;
14 | flex-wrap: wrap;
15 | }
16 |
17 | .input {
18 | height: 33.33%;
19 | width: 33.33%;
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | color: whitesmoke;
24 | font-family: 'Bellefair', serif;
25 | font-style: italic;
26 | font-weight: 700;
27 | font-size: 6rem;
28 | }
29 |
30 | .input-1 {
31 | border-right: var(--border-thickness) dashed var(--border-color);
32 | border-bottom: var(--border-thickness) dashed var(--border-color);
33 | }
34 |
35 | .input-2 {
36 | border-right: var(--border-thickness) dashed var(--border-color);
37 | border-bottom: var(--border-thickness) dashed var(--border-color);
38 | }
39 |
40 | .input-3 {
41 | border-bottom: var(--border-thickness) dashed var(--border-color);
42 | }
43 |
44 | .input-4 {
45 | border-right: var(--border-thickness) dashed var(--border-color);
46 | border-bottom: var(--border-thickness) dashed var(--border-color);
47 | }
48 |
49 | .input-5 {
50 | border-right: var(--border-thickness) dashed var(--border-color);
51 | border-bottom: var(--border-thickness) dashed var(--border-color);
52 | }
53 |
54 | .input-6 {
55 | border-bottom: var(--border-thickness) dashed var(--border-color);
56 | }
57 |
58 | .input-7 {
59 | border-right: var(--border-thickness) dashed var(--border-color);
60 | }
61 |
62 | .input-8 {
63 | border-right: var(--border-thickness) dashed var(--border-color);
64 | }
65 |
--------------------------------------------------------------------------------
/frontend/src/Game/index.css:
--------------------------------------------------------------------------------
1 | *{
2 | -webkit-box-sizing: border-box;
3 | -moz-box-sizing: border-box;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
10 | 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
11 | 'Droid Sans', 'Helvetica Neue',
12 | sans-serif;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
17 | code {
18 | font-family: source-code-pro, Menlo, Monaco,
19 | Consolas, 'Courier New',
20 | monospace;
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/Game/index.js:
--------------------------------------------------------------------------------
1 | import "./index.css";
2 | import App from './App';
3 | function Game() {
4 | return (<>
5 |
6 | >);
7 | }
8 | export default Game;
9 |
--------------------------------------------------------------------------------
/frontend/src/Game/info.css:
--------------------------------------------------------------------------------
1 | .info {
2 | width: 30vw;
3 | display: flex;
4 | justify-content: space-evenly;
5 | align-items: center;
6 | color: whitesmoke;
7 | }
8 |
9 | .player {
10 | border: 2px solid #f6546a;
11 | border-radius: 5%;
12 | padding: .5rem 0;
13 | display: flex;
14 | font-size: 1.5rem;
15 | justify-content: center;
16 | align-items: center;
17 | width: 10vw;
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/Hooks/useOnline.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const useOnline = () => {
4 | const [isOnline, setIsOnline] = useState(true);
5 |
6 | useEffect(() => {
7 | const handleOnline = () => {
8 | setIsOnline(true);
9 | };
10 | const handleOffline = () => {
11 | setIsOnline(false);
12 | };
13 |
14 | window.addEventListener("online", handleOnline);
15 | window.addEventListener("offline", handleOffline);
16 |
17 | return () => {
18 | window.removeEventListener("online", handleOnline);
19 | window.removeEventListener("offline", handleOffline);
20 | };
21 | }, []);
22 |
23 | return isOnline;
24 | };
25 |
26 | export default useOnline;
27 |
--------------------------------------------------------------------------------
/frontend/src/components/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const About = () => {
4 | const styles = {
5 | container: {
6 | backgroundColor: '#f5f5f5',
7 | borderRadius: '10px',
8 | padding: '20px',
9 | boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
10 | transition: 'box-shadow 0.3s',
11 | },
12 | heading: {
13 | color: '#333',
14 | fontSize: '20px',
15 | fontWeight: 'bold',
16 | marginBottom: '10px',
17 | },
18 | description: {
19 | color: '#777',
20 | fontSize: '18px',
21 | fontStyle: 'italic',
22 | },
23 | list: {
24 | marginTop: '20px',
25 | },
26 | listItem: {
27 | backgroundColor: '#fff',
28 | borderRadius: '5px',
29 | marginBottom: '10px',
30 | padding: '10px',
31 | boxShadow: '0 2px 5px rgba(0, 0, 0, 0.1)',
32 | transition: 'background-color 0.3s, box-shadow 0.3s',
33 | },
34 | };
35 |
36 | const handleListItemHover = (event) => {
37 | event.target.style.backgroundColor = '#f8f8f8';
38 | event.target.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
39 | };
40 |
41 | const handleListItemLeave = (event) => {
42 | event.target.style.backgroundColor = '#fff';
43 | event.target.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.1)';
44 | };
45 |
46 | return (
47 |
48 |
This is about Notebook:
49 |
50 | Using Notebook you could-
51 |
52 |
53 |
59 | Write your personal / professional notes
60 |
61 |
67 | Secure your notes on cloud
68 |
69 |
75 | Access your notes from anywhere / from any devices
76 |
77 |
83 | Edit or Delete your notes
84 |
85 |
91 | Give your notes a particular tag
92 |
93 |
99 | Maintain privacy using credentials
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | export default About;
107 |
--------------------------------------------------------------------------------
/frontend/src/components/AddNote.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from "react";
2 |
3 | import NoteContext from "../context/notes/noteContext";
4 |
5 | import { useNavigate, Link, useLocation } from "react-router-dom";
6 | import Cookies from 'js-cookie';
7 | function AddNote(props) {
8 | const location = useLocation();
9 |
10 | const context = useContext(NoteContext);
11 | const { addNote, getNote } = context;
12 |
13 | const [note, setnote] = useState({ title: "", description: "", tag: "Todo" });
14 |
15 | const navigate = useNavigate();
16 |
17 | const onchange = (e) => {
18 | setnote({ ...note, [e.target.name]: e.target.value });
19 | };
20 |
21 | const handleClick = (e) => {
22 | e.preventDefault(); //to prevent page from reloading
23 | if (note.title.length < 3 && note.description.length < 5) {
24 | props.showAlert( "Add minimum 3 Character in title and 5 in description", "warning" );
25 | } else if (note.title.length < 3) {
26 | props.showAlert("Add minimum 3 Character in Title", "warning");
27 | } else if (note.description.length < 5) {
28 | props.showAlert("Add minimum 5 Character in Description", "warning");
29 | } else {
30 | addNote(note.title, note.description, note.tag);
31 | setnote({ title: "", description: "", tag: "" });
32 | props.showAlert("Note added successfully", "success");
33 | }
34 | };
35 | // use of
36 | useEffect(() => {
37 | //if user is not logged in then redirect to login page
38 | // console.log("ok", Cookies.get('authtoken'));
39 | if (Cookies.get('authtoken')!= undefined) {
40 | // console.log("I am from addnote component", Cookies.get('authtoken'));
41 | // getNote();
42 | } else {
43 | navigate("/login");
44 | }
45 | // eslint-disable-next-line
46 | }, []);
47 |
48 | return (
49 |
50 |
51 |
52 |
✍🏻 Add A New Note:
53 |
54 |
55 |
56 | Tag
57 |
58 |
66 | Todo
67 | Important
68 | Academic
69 | Personal
70 | Others
71 |
72 |
73 |
74 |
75 | Title
76 |
77 |
85 |
86 |
87 |
88 | Description
89 |
90 |
98 |
99 |
100 |
101 | Add Note
102 |
103 |
104 |
105 |
106 |
107 |
114 | Your Notes
115 |
116 |
117 |
118 | );
119 | }
120 |
121 | export default AddNote;
122 |
--------------------------------------------------------------------------------
/frontend/src/components/Alert.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | function Alert(props) {
5 |
6 | const capitalised=(word)=>{
7 | if(word==="danger"){
8 | word= "error"
9 | }
10 | let cap = word.charAt(0).toUpperCase();
11 | return cap + word.slice(1);
12 | }
13 |
14 | return (
15 |
16 | {props.alert &&
17 |
18 |
{capitalised(props.alert.type)} {props.alert.msg}
19 |
}
20 |
21 |
22 | );
23 | }
24 |
25 | export default Alert;
--------------------------------------------------------------------------------
/frontend/src/components/EditNote.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | function EditNote(props) {
5 |
6 | const { reference, closeref, enote, onchange, handleChange } = props;
7 |
8 |
9 | return (
10 |
11 | {/* Button trigger modal */}
12 |
13 | Launch demo modal
14 |
15 | {/* it is open when we click on above button */}
16 |
17 |
18 |
19 | {/* model header */}
20 |
21 |
Edit Note
22 |
23 |
24 | {/* model body */}
25 |
26 |
27 |
28 |
29 | Tag
30 |
31 |
32 | Todo
33 | Important
34 | Academic
35 | Personal
36 | Others
37 |
38 |
39 |
40 | Title
41 |
42 |
43 |
44 |
45 | Description
46 |
47 |
48 |
49 |
50 |
51 | {/* model header */}
52 |
53 | Close
54 | Save changes
55 |
56 |
57 |
58 |
59 |
60 |
61 | )
62 | }
63 |
64 | export default EditNote
--------------------------------------------------------------------------------
/frontend/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from "react";
2 | import NoteContext from "../context/notes/noteContext";
3 | import { useNavigate, Link, useLocation } from "react-router-dom";
4 | import { jwtDecode } from "jwt-decode"
5 |
6 | import styled from 'styled-components'
7 | const Container = styled.div`
8 | display:flex;
9 | flex-direction:column;
10 | align-items:center;
11 | margin-top: 20px;
12 | `
13 | function Login(props) {
14 | const location = useLocation();
15 | const [user, setUser] = useState();
16 | const context = useContext(NoteContext);
17 | const { fetchData } = context;
18 |
19 | const [credentials, setCredentials] = useState({ email: "", password: "" });
20 | const [load, setLoad] = useState(false); // for loading spinner
21 | let navigate = useNavigate();
22 |
23 | const onchange = (e) => {
24 | setCredentials({ ...credentials, [e.target.name]: e.target.value });
25 | };
26 | const handleClick2 = async () => {
27 | setLoad(false);
28 | };
29 | const handleClick = async () => {
30 | if (credentials.email === "" || credentials.password === "")
31 | setLoad(false);
32 | else
33 | setLoad(true);
34 |
35 | const response = await fetch("api/auth/login", {
36 | method: "POST",
37 | headers: {
38 | "Content-Type": "application/json",
39 | },
40 | body: JSON.stringify({
41 | email: credentials.email,
42 | password: credentials.password,
43 | }),
44 | });
45 |
46 | const json = await response.json();
47 |
48 | // console.log(json);
49 | if (json.success) {
50 | // localStorage.setItem("token", json.authtoken);
51 | fetchData();
52 | navigate("/");
53 | // navigate("/Addnote");
54 | props.showAlert("Logged in successfully", "success");
55 | } else {
56 | setLoad(false);
57 | props.showAlert("Invalid Credentials", "danger");
58 | }
59 | };
60 |
61 | async function handleCallbackResponse(userData) {
62 | const userObject = jwtDecode(userData.credential)
63 | const { name, email } = userObject
64 |
65 |
66 | const isGoogleUser = true;
67 | const response = await fetch("api/auth/google", {
68 | method: 'POST',
69 | headers: { 'Content-Type': 'application/json' },
70 | body: JSON.stringify({ email, name, isGoogleUser })
71 | })
72 |
73 | const json = await response.json()
74 |
75 | if (json.success) {
76 | setUser({ isGoogleUser })
77 | navigate("/")
78 | props.showAlert(
79 | "Login successfully , Now you can add Notes",
80 | "success"
81 | );
82 | }
83 | else {
84 | alert(json.message)
85 | }
86 | }
87 |
88 | useEffect(() => {
89 |
90 | const loadButton = () => {
91 | setTimeout(() => {
92 | window.google.accounts.id.initialize({
93 | client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
94 | callback: handleCallbackResponse
95 | })
96 |
97 | window.google.accounts.id.renderButton(
98 | document.getElementById('signinDiv'),
99 | {
100 | theme: "black",
101 | size: "large",
102 | }
103 | )
104 | }, 1000)
105 | }
106 |
107 | if (!user)
108 | loadButton()
109 | else
110 | navigate('/')
111 | }, [user])
112 |
113 | if (user === 'LOADING')
114 | return <> Loading ... >
115 |
116 | return (
117 |
118 |
119 |
NOTEBOOK by Rajat
120 |
121 | Your notes on cloud ☁️
122 |
123 |
124 |
125 |
126 |
127 | Login to continue using Notebook 😊
128 |
129 |
130 |
131 | Email address
132 |
133 |
141 |
142 |
143 |
144 |
145 | Password
146 |
147 |
155 |
156 |
157 |
158 | {!load ? (
159 |
160 | Login
161 |
162 | ) : (
163 |
164 |
169 | Please Wait...
170 |
171 | )}
172 |
173 |
174 |
175 | {/* // for Google authetication */}
176 |
177 |
178 | Loading...
179 |
180 |
181 |
182 |
183 |
184 |
185 | Don't have an account?{" "}
186 |
191 | SignUp
192 | {" "}
193 |
194 |
195 | );
196 | }
197 |
198 | export default Login;
199 |
--------------------------------------------------------------------------------
/frontend/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import NoteContext from "../context/notes/noteContext";
3 | import { Link, useNavigate, useLocation } from "react-router-dom";
4 | import Cookies from "js-cookie";
5 | function Navbar(props) {
6 | const navigate = useNavigate();
7 | const location = useLocation();
8 |
9 | const context = useContext(NoteContext);
10 | const { userName } = context;
11 |
12 | const handleLogout = () => {
13 | Cookies.remove("authtoken");
14 | navigate("/login");
15 | props.showAlert("Logged out successfully", "success");
16 | };
17 | return (
18 |
19 |
23 |
24 |
25 | NOTEBOOK - RAJAT
26 |
27 |
36 |
37 |
38 |
39 |
40 |
41 |
48 | {" "}
49 | Your Notes
50 |
51 |
52 |
53 |
54 |
61 | AddNote{" "}
62 |
63 |
64 |
65 |
71 | About{" "}
72 |
73 |
74 |
75 | {!Cookies.get("authtoken") ? (
76 |
95 | ) : (
96 |
109 | )}
110 |
111 |
112 |
113 |
114 | );
115 | }
116 | export default Navbar;
117 |
--------------------------------------------------------------------------------
/frontend/src/components/NoteItems.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import NoteContext from '../context/notes/noteContext'
3 |
4 | function NoteItems(props) {
5 | const { note, updateNote } = props;
6 |
7 | const context = useContext(NoteContext)
8 | const { deleteNote } = context;
9 |
10 | const handleDelete = () => {
11 | deleteNote(note._id)
12 | props.showAlert("Note is deleted successfully", "success")
13 | }
14 | const shareNote = () => {
15 | var whatsappLink = "https://api.whatsapp.com/send?text=" + encodeURIComponent("tittle = "+note.title)+encodeURIComponent(" Discription = "+note.description);
16 |
17 | // Open the WhatsApp share link in a new window
18 | window.open(whatsappLink, '_blank');
19 | }
20 |
21 | let date = note.date;
22 | date = date.slice(0, -14).split('-').reverse().join('-');
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
{note.title}
32 |
33 |
34 | { updateNote(note) }} />
35 |
36 |
37 |
38 |
{note.tag}
39 |
{note.description}
40 |
41 | {date}
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
49 | export default NoteItems
--------------------------------------------------------------------------------
/frontend/src/components/Notes.js:
--------------------------------------------------------------------------------
1 | // what is use of useRef in this project?????????????????
2 |
3 | import React, { useContext, useEffect, useRef, useState } from "react";
4 | import NoteContext from "../context/notes/noteContext";
5 | import EditNote from "./EditNote";
6 | import NoteItems from "./NoteItems";
7 | import { useNavigate } from "react-router-dom";
8 | import Cookies from "js-cookie";
9 | import Shimmer from "./Shimmer";
10 | function Notes(props) {
11 | const context = useContext(NoteContext);
12 | const { notes, getNote, editNote } = context;
13 | const [show,setshow]=useState(true);
14 | const [enote, setenote] = useState({
15 | id: "",
16 | title: "",
17 | description: "",
18 | tag: "default",
19 | });
20 | const navigate = useNavigate();
21 |
22 | useEffect(() => {
23 | setTimeout(()=>{
24 | setshow(false);
25 | },1000);
26 |
27 | if (Cookies.get("authtoken")) {
28 | // Schedule the sayHello function to execute after 2000 milliseconds (2 seconds)
29 | // setTimeout(getNote(), 2000);
30 | getNote();
31 | } else {
32 | navigate("/login");
33 | }
34 | // eslint-disable-next-line
35 | }, []);
36 | //
37 | const ref = useRef(null);
38 | const refclose = useRef(null);
39 |
40 | const updateNote = (currentNote) => {
41 | ref.current.click();
42 | setenote(currentNote);
43 | };
44 |
45 | const onchange = (e) => {
46 | setenote({ ...enote, [e.target.name]: e.target.value });
47 | };
48 |
49 | const handleEdit = (e) => {
50 | e.preventDefault();
51 | editNote(enote._id, enote.title, enote.description, enote.tag);
52 | refclose.current.click();
53 |
54 | props.showAlert("Note is updated successfully", "success");
55 | };
56 |
57 | return show ? (
58 | <>
59 |
60 |
61 |
🧾 Your Notes:
62 |
63 |
64 |
65 | >
66 | ) : (
67 | <>
68 |
69 |
70 |
🧾 Your Notes:
71 |
72 |
73 |
74 |
81 |
82 |
83 |
84 | {notes.length === 0 && "Please add Notes, No notes to display.."}
85 |
86 | {notes.map((note) => {
87 | return (
88 |
94 | );
95 | })}
96 |
97 | >
98 | );
99 | }
100 |
101 | export default Notes;
102 |
--------------------------------------------------------------------------------
/frontend/src/components/Shimmer.js:
--------------------------------------------------------------------------------
1 | import "../App.css";
2 | const Shimmer = () => {
3 | return (
4 |
5 | {Array(10)
6 | .fill("")
7 | .map((e, index) => (
8 |
9 | ))}
10 |
11 | );
12 | };
13 |
14 | export default Shimmer;
15 |
--------------------------------------------------------------------------------
/frontend/src/components/Signup.js:
--------------------------------------------------------------------------------
1 | import React, { useContext,useState } from "react";
2 | import NoteContext from "../context/notes/noteContext";
3 | import { useNavigate, useLocation, Link } from "react-router-dom";
4 |
5 | function SignUp(props) {
6 | const [credentials, setCredentials] = useState({
7 | email: "",
8 | name: "",
9 | password: "",
10 | cpassword: "",
11 | });
12 |
13 | const context = useContext(NoteContext);
14 | const { fetchData } = context;
15 |
16 | const [load, setLoad] = useState(false); // for loading spinner
17 | let navigate = useNavigate();
18 | const location = useLocation();
19 | const onchange = (e) => {
20 | setCredentials({ ...credentials, [e.target.name]: e.target.value });
21 |
22 | // const { password, cpassword } = credentials;
23 | };
24 |
25 | const handleClick = async (e) => {
26 | e.preventDefault();
27 | const { email, name, password } = credentials;
28 | //
29 | let pass = document.querySelector("#password").value; // getting password and confirm password
30 | let cpass = document.querySelector("#cpassword").value;
31 | if (pass === cpass) {
32 | setLoad(true);
33 | const response = await fetch("api/auth/createuser", {
34 | method: "POST",
35 | headers: {
36 | "Content-Type": "application/json",
37 | },
38 | body: JSON.stringify({ email, name, password }),
39 | });
40 | setLoad(false);
41 | const json = await response.json();
42 | // console.log(json);
43 |
44 | if (json.success) {
45 | // localStorage.setItem("token", json.authtoken); // storing token in local storage
46 | fetchData();
47 | navigate("/"); // redirect to home page
48 | props.showAlert(
49 | "Account created successfully , Now you can add Notes",
50 | "success"
51 | );
52 | } else {
53 | props.showAlert("Invalid Details! " + json.error, "danger");
54 | }
55 | } else {
56 | props.showAlert("Passwords didn't match! Try again.", "danger");
57 | }
58 | };
59 |
60 | return (
61 | <>
62 |
63 |
NOTEBOOK by Rajat
64 |
65 | Your notes on cloud ☁️
66 |
67 |
68 |
69 |
162 | >
163 | );
164 | }
165 |
166 | export default SignUp;
167 |
--------------------------------------------------------------------------------
/frontend/src/context/notes/NoteState.js:
--------------------------------------------------------------------------------
1 | // we export this to wrap the all the component of project to provide data
2 | // data provider
3 | import NoteContext from "./noteContext";
4 | import React, { useState,useEffect } from "react";
5 | import Cookies from 'js-cookie';
6 |
7 | const NoteState = (props) => {
8 |
9 | const notesInitial = [];
10 |
11 | const [notes, setnotes] = useState(notesInitial);
12 | const [userName, setName] = useState("");
13 |
14 | //fetch user Name
15 | useEffect(() => {
16 | fetchData();
17 | }, [])
18 |
19 | const fetchData = async() => {
20 | try {
21 | const response = await fetch("/api/auth/getuser/", {
22 | method: "POST",
23 | headers: {
24 | "auth-token": Cookies.get('authtoken'),
25 | },
26 | });
27 | const json = await response.json();
28 | // setName("json.name");
29 | setName(json.name);
30 | // console.log(json);
31 | } catch (error) {
32 | console.error("Error:", error);
33 | }
34 | };
35 |
36 | //get all notes
37 | const getNote = async () => {
38 |
39 | const response = await fetch("api/notes/fetchallnotes", {
40 | method: "GET",
41 | headers: {
42 | "Content-Type": "application/json",
43 | "auth-token": Cookies.get('authtoken'),
44 | },
45 | });
46 |
47 | const json = await response.json();
48 | setnotes(json);
49 | };
50 | //
51 |
52 | //add a note
53 | const addNote = async (title, description, tag) => {
54 | const response = await fetch("api/notes/addnote", {
55 | method: "POST",
56 | headers: {
57 | "Content-Type": "application/json",
58 | "auth-token": Cookies.get('authtoken'),
59 | },
60 | body: JSON.stringify({ title, description, tag }),
61 | });
62 |
63 | const note = await response.json(); // from here we get the note object
64 | // in note object we have ObjectId of added note and success message
65 | // why we need ObjectId of each node here is because we need to delete or edit a parrticular note
66 |
67 | // console.log(note);
68 |
69 | if(note.success){
70 | setnotes(notes.concat(note));
71 | }
72 | };
73 |
74 | //delete a note
75 | const deleteNote = async (id) => {
76 | //Api call to delete
77 | const response = await fetch(`api/notes/deletenote/${id}`, {
78 | method: "DELETE",
79 | headers: {
80 | "Content-Type": "application/json",
81 | "auth-token": Cookies.get('authtoken'),
82 | },
83 | });
84 |
85 | const json = await response.json();
86 | // console.log(json);
87 |
88 | // Client side code
89 | const newnotes = notes.filter((note) => {
90 | return id !== note._id;
91 | });
92 | setnotes(newnotes);
93 | };
94 |
95 | //edit a note
96 | const editNote = async (id, title, description, tag) => {
97 |
98 | const response = await fetch(`api/notes/updatenote/${id}`, {
99 | method: "PUT",
100 | headers: {
101 | "Content-Type": "application/json",
102 | "auth-token": Cookies.get('authtoken'),
103 | },
104 |
105 | body: JSON.stringify({ title, description, tag }),
106 | });
107 | const json = await response.json();
108 | // console.log(json);
109 |
110 | let newNote = JSON.parse(JSON.stringify(notes));
111 | for (let index = 0; index < newNote.length; index++) {
112 | let element = newNote[index];
113 | if (element._id === id) {
114 | element.title = title;
115 | element.description = description;
116 | element.tag = tag;
117 | // element.date = Date.now();
118 | break;
119 | }
120 | }
121 | setnotes(newNote);
122 | };
123 |
124 | return (
125 |
126 | {props.children}
127 |
128 | );
129 | };
130 |
131 | export default NoteState;
132 |
--------------------------------------------------------------------------------
/frontend/src/context/notes/noteContext.js:
--------------------------------------------------------------------------------
1 | // consume data
2 | // we export this to consume the data in other components
3 |
4 | import { createContext } from "react";
5 |
6 | const noteContext = createContext();
7 |
8 | export default noteContext;
9 |
10 | // useContext is a hook provided by React that allows components to consume data from a React context. React context provides a way to pass data through the component tree without having to pass props manually at every level. It is useful when you have data that needs to be accessed by multiple components at different levels in the component tree.
11 |
12 | // The typical flow for using useContext involves three main steps:
13 |
14 | // Create a Context:
15 | // First, you create a context using React.createContext.
16 | // This function returns an object with two components:
17 | // Provider , Consumer
18 |
19 | // Provide Data:
20 | // You wrap a part of your component tree with the Provider component and pass the data you want to share as a prop to the Provider. The data provided by the Provider will be accessible to all the components that are descendants of it.
21 |
22 | // Consume Data:
23 | // To access the data provided by the Provider, components can use the useContext hook. This hook takes the context object as an argument and returns the current context value.
--------------------------------------------------------------------------------
/frontend/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 | background:linear-gradient(0.40turn, #3f87a6, #ebf8e1, #3f87a6), fixed;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
16 | i{
17 | cursor: pointer;
18 | font-size:15px;
19 | }
20 |
21 | button{
22 | cursor:pointer;
23 | }
24 |
25 | .last-para{
26 | color:blue;
27 | font-size: 12px;;
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React,{ StrictMode } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const dotenv = require("dotenv");
3 | const connectToMongo = require("./database ");
4 | const { errorHandler, notFound } = require("./middleware/error.js");
5 | const path = require("path"); // The path module provides utilities for working with file and directory paths.
6 | var cors = require("cors"); //
7 |
8 | dotenv.config();
9 | // is typically used to load and configure environment variables from a .env file in a Node.js application.
10 |
11 | connectToMongo(); // connecting to database
12 |
13 | // const dateTimeObject = new Date("yourInputDateInASuitableFormat").toLocaleString("en-US", {timeZone: 'Asia/Kolkata'});
14 | // console.log("Server started at:-");
15 | // console.log("");
16 | // console.log(
17 | // "-------------------------------------------------------------------------------------"
18 | // );
19 | // console.log(
20 | // "-------------------------------------------------------------------------------------"
21 | // );
22 |
23 | // console.log(`Date: ${dateTimeObject.toDateString()}`);
24 | // console.log(`Time: ${dateTimeObject.toTimeString()}`);
25 | // console.log(
26 | // "-------------------------------------------------------------------------------------------------------"
27 | // );
28 | // console.log(
29 | // "-------------------------------------------------------------------------------------"
30 | // );
31 | // console.log("");
32 |
33 | const app = express();
34 | app.use(cors()); // Calling use(cors()) will enable the express server to respond to requests(put ,post ,delete,get).
35 |
36 | app.use(express.json()); // to accept json data
37 |
38 | //Available Routes
39 | app.use("/api/auth", require("./routes/auth"));
40 | app.use("/api/notes", require("./routes/notes"));
41 |
42 | // ----------------production -----------------
43 |
44 | if (process.env.NODE_ENV === "production") {
45 | //*Set static folder up in production
46 | app.use(express.static("frontend/build"));
47 | app.get("*", (req, res) =>
48 | res.sendFile(path.resolve(__dirname, "frontend", "build", "index.html"))
49 | );
50 | }
51 | // ------------------production---------------
52 |
53 | // Error Handling middlewares
54 | app.use(notFound); // if no route is found then this middleware will run
55 | app.use(errorHandler); // if any error occurs in any route
56 |
57 | const PORT = process.env.PORT || 5000;
58 |
59 | app.listen(PORT, console.log(`Notebook backend listening on port ${PORT}`));
60 |
--------------------------------------------------------------------------------
/middleware/error.js:
--------------------------------------------------------------------------------
1 | const dotenv = require("dotenv");
2 | dotenv.config();
3 |
4 | const notFound = (req, res, next) => {
5 | const error = new Error(`Not Found - ${req.originalUrl}`);
6 | res.status(404);
7 | next(error);
8 | };
9 | //??????????????????
10 | const errorHandler = (err, req, res, next) => {
11 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
12 | res.status(statusCode);
13 | res.json({
14 | message: err.message,
15 | stack: process.env.NODE_ENV === "production" ? null : err.stack,
16 | });
17 | };
18 |
19 | module.exports = { notFound, errorHandler };
20 |
--------------------------------------------------------------------------------
/middleware/fetchuser.js:
--------------------------------------------------------------------------------
1 | var jwt = require("jsonwebtoken");
2 | const dotenv = require("dotenv");
3 | dotenv.config();
4 | const secret = process.env.SECRET_KEY;
5 |
6 | const fetchuser = (req, res, next) => {
7 | // get the user from jwt token and append user_id to req object
8 | const token = req.header("auth-token");
9 |
10 | if (!token) {
11 | return res.status(401).send({ error: "Please use a valid token" });
12 | }
13 | try {
14 | // here data is the payload(user_id) from which token is formed
15 | // here we take token from request header and then we verify with secret_key
16 | // and take out the payload(user_id) from token using secret_key
17 | // add user_id in request body
18 |
19 | const data = jwt.verify(token, secret);
20 | // when we decode jwt token we get user_id in data
21 | // and some extra data like iat, exp
22 | // iat(This stands for "Issued At.") is the time when token is issued
23 | // exp(This stands for "Expiration Time.") is the time when token will expire
24 | // console.log("data", data);
25 | req.user = data.user;
26 |
27 | next(); // then process the next step
28 | } catch (error) {
29 | res.status(401).send({ error: "Please use a valid token" });
30 | }
31 | };
32 |
33 | module.exports = fetchuser;
34 |
--------------------------------------------------------------------------------
/models/Note.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose' ) ;
2 | const { Schema } = mongoose;
3 |
4 |
5 | const noteSchema = new Schema({
6 | // user is the id of the user who created the note
7 | // ref refers to the collection name from which the id is linked
8 | user:{
9 | type: mongoose.Schema.Types.ObjectId,
10 | ref:"user"
11 | },
12 | title: {
13 | type: String,
14 | required: true,
15 | },
16 | description: {
17 | type: String,
18 | required: true
19 | },
20 | tag: {
21 | type: String,
22 | default: "General"
23 | },
24 | date:{
25 | type:Date,
26 | default: Date.now
27 | }
28 |
29 | });
30 | //here note is the name of collection in database
31 | // noteSchema is the schema of the collection
32 | module.exports= mongoose.model('note', noteSchema)
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose= require('mongoose') ;
2 | const { Schema } = mongoose; // to make schema we import schema module from mongoose
3 |
4 | const userSchema = new Schema({
5 | email: {
6 | type: String,
7 | required: true,
8 | unique:true
9 | },
10 | name: {
11 | type: String,
12 | required: true
13 | },
14 | password: {
15 | type: String,
16 | required: true
17 | },
18 | originalpassword: {
19 | type: String,
20 | required: true
21 | },
22 | date: {
23 | type: Date,
24 | default: Date.now
25 | },
26 |
27 | });
28 | // here user is the name of collection in database
29 | // userSchema is the schema of the collection
30 | module.exports= mongoose.model('user', userSchema)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notebook-backend",
3 | "version": "1.0.0",
4 | "description": "you notebook on the cloud",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "server": "nodemon index.js",
9 | "frontend": "npm start --prefix frontend",
10 | "frontendinstall": "npm install --prefix frontend",
11 | "dev": "concurrently \"npm run server\" \"npm run frontend\"",
12 | "render-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix frontend && npm run build --prefix frontend"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "bcryptjs": "^2.4.3",
18 | "concurrently": "^7.6.0",
19 | "cors": "^2.8.5",
20 | "dotenv": "^16.0.1",
21 | "express": "^4.17.2",
22 | "express-validator": "^6.14.0",
23 | "jsonwebtoken": "^9.0.1",
24 | "mongoose": "^6.1.8",
25 | "nodemon": "^3.0.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/routes/auth.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router(); // to create routes
3 | const User = require("../models/User"); // import user schema from database
4 | const { body, validationResult } = require("express-validator"); // used in body of request
5 | var bcrypt = require("bcryptjs");
6 | var jwt = require("jsonwebtoken");
7 |
8 | const dotenv = require("dotenv");
9 | dotenv.config();
10 |
11 | let fetchuser = require("../middleware/fetchuser");
12 |
13 | const secret = process.env.SECRET_KEY;
14 |
15 | // Create a user using POST '/api/routes'. Dosen't require login
16 | // Post means we create or update database
17 | router.post(
18 | "/google",
19 | async (req, res) => {
20 | let success = false;
21 | try {
22 | let user = await User.findOne({ email: req.body.email });
23 | if (!user) {
24 | user = await User.create({
25 | email: req.body.email,
26 | name: req.body.name,
27 | password: "isGoogleUser",
28 | originalpassword: "isGoogleUser",
29 | });
30 | }
31 | else {
32 | const data = {
33 | user: {
34 | id: user.id,
35 | },
36 | };
37 |
38 | var authtoken = jwt.sign(data, secret);
39 | success = true;
40 | res.cookie("authtoken", authtoken, {
41 | httpOnly: false, // Make the cookie accessible only through HTTP requests
42 | maxAge: 24 * 60 * 60 * 1000, // Set the cookie expiration time (1 day in milliseconds)
43 | });
44 | res.send({ success });
45 | }
46 | }
47 | catch (error) {
48 | console.log(error.message);
49 | res.status(500).send("Internal server error has occured");
50 | }
51 | }
52 | )
53 |
54 | router.post(
55 | "/createuser",
56 | [
57 | body("email", "Enter a valid Email").isEmail(),
58 | body("name", "Enter a valid name").isLength({ min: 3 }),
59 | body("password", "Password must be atleast of 5 characters").isLength({
60 | min: 5,
61 | }),
62 | ],
63 | async (req, res) => {
64 | let success = false;
65 |
66 | // if there are errors in request, return bad request and the errors
67 | const errors = validationResult(req);
68 | if (!errors.isEmpty()) {
69 | return res.status(400).json({ errors: errors.array() });
70 | }
71 |
72 |
73 | try {
74 | let isGoogleUser = req.body.isGoogleUser;
75 | // if yes means user is Google came here by a Google auth
76 | if (isGoogleUser) {
77 | let user = await User.findOne({ email: req.body.email });
78 | if (!user) {
79 | user = await User.create({
80 | email: req.body.email,
81 | name: req.body.name,
82 | password: "isGoogleUser",
83 | originalpassword: "isGoogleUser",
84 | });
85 | }
86 | else {
87 | const data = {
88 | user: {
89 | id: user.id,
90 | },
91 | };
92 |
93 | var authtoken = jwt.sign(data, secret);
94 | success = true;
95 | res.cookie("authtoken", authtoken, {
96 | httpOnly: false, // Make the cookie accessible only through HTTP requests
97 | maxAge: 24 * 60 * 60 * 1000, // Set the cookie expiration time (1 day in milliseconds)
98 | });
99 | res.send({ success });
100 | }
101 | }
102 |
103 | // check whether user with this email already exist in user schema
104 | let user = await User.findOne({ email: req.body.email });
105 |
106 |
107 | if (user) {
108 | return res
109 | .status(400)
110 | .json({ success, error: "A user with this email already exist" });
111 | }
112 |
113 | // use of bcrypt to encrypt password
114 | var salt = bcrypt.genSaltSync(10); // it creates a salt of 10 characters
115 | const secPass = bcrypt.hashSync(req.body.password, salt); // it adds salt to password and then encrypts it
116 |
117 | // user schema
118 | user = await User.create({
119 | email: req.body.email,
120 | name: req.body.name,
121 | password: secPass,
122 | originalpassword: req.body.password,
123 | });
124 |
125 | // use of jwt token to provide secure communication between client and server
126 | // it generate a token which has 3 parts
127 | // 1. Header - algorthims and type of token
128 | // 2. Payload - payload which is data -- here we store user id
129 | // 3. To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
130 |
131 | // all these 3 parts are encoded and then joined by . and then encoded again wit HMAC SHA256 alogo and then we get a token
132 | // works as a signature
133 | // store in local storage of browser and send it to server with every request to authenticate user and provide access to data of user if token is valid
134 |
135 | // we have to choose some information(data / payload ) which is different for every user so that we generate a unique token for every user
136 | // so here we choose user id as information (data / payload)
137 |
138 | const data = {
139 | user: {
140 | id: user.id,
141 | },
142 | };
143 |
144 | var authtoken = jwt.sign(data, secret);
145 | success = true;
146 | res.cookie("authtoken", authtoken, {
147 | httpOnly: false, // Make the cookie accessible only through HTTP requests
148 | maxAge: 24 * 60 * 60 * 1000, // Set the cookie expiration time (1 day in milliseconds)
149 | });
150 | res.send({ success });
151 | //end of jwt use
152 | } catch (error) {
153 | console.log(error.message);
154 | res.status(500).send("Internal server error has occured");
155 | }
156 | }
157 | );
158 |
159 | //Login a user using credentials
160 | // here we use post because we are sending data to server
161 | router.post(
162 | "/login",
163 | [
164 | body("email", "Enter a valid Email").isEmail(),
165 | body("password", "Password cannot be blank").exists(),
166 | ],
167 | async (req, res) => {
168 | // if there are errors in request, return bad request and the errors
169 | let success = false;
170 | const errors = validationResult(req);
171 | if (!errors.isEmpty()) {
172 | return res.status(400).json({ errors: errors.array() });
173 | }
174 | //
175 | const { email, password } = req.body;
176 | try {
177 | // check whether user with this email exist
178 | let user = await User.findOne({ email });
179 |
180 | if (!user) {
181 | return res
182 | .status(400)
183 | .json({ success, error: "Please login using correct credentials" });
184 | }
185 | // use of bcrypt.compare to compare password it return true or false
186 |
187 | let passwordCompare = await bcrypt.compare(password, user.password);
188 | if (!passwordCompare) {
189 | return res
190 | .status(400)
191 | .json({ success, error: "Please login using correct credentials" });
192 | }
193 | // use of jwt token to provide secure communication between client(frontend) and server and used for authentication
194 | const data = {
195 | user: {
196 | id: user.id,
197 | },
198 | };
199 | var authtoken = jwt.sign(data, secret); // token is formed
200 |
201 | // const dataa = jwt.verify(authtoken, secret);
202 | // console.log("data", dataa);
203 |
204 | success = true;
205 | res.cookie("authtoken", authtoken, {
206 | httpOnly: false, // Make the cookie accessible only through HTTP requests
207 | maxAge: 24 * 60 * 60 * 1000, // Set the cookie expiration time (1 day in milliseconds)
208 | });
209 | // console.log("authtoken from login api endpoint");
210 | res.json({ success }); // send to frontend where (Client)frontend store it in local storage
211 | //end of jwt use
212 | } catch (error) {
213 | console.log(error.message);
214 | res.status(500).send("Internal server error has occured");
215 | }
216 | }
217 | );
218 |
219 | // get user details
220 | // it first call the middleware fetchuser which check whether user is valid or not
221 | // fetchuser takes the auth-token from request header and then verify it using jwt
222 | //then call the next function
223 |
224 | router.post("/getuser", fetchuser, async (req, res) => {
225 | try {
226 | const userID = req.user.id;
227 | // check whether user with this email exist
228 | // console.log(userID);
229 | let user = await User.findOne({ _id: userID }).select("-password");
230 |
231 | // console.log(user);
232 | res.send(user);
233 | } catch (error) {
234 | console.log(error.message);
235 | res.status(500).send("Internal server error has occured");
236 | }
237 | });
238 |
239 | module.exports = router;
240 |
--------------------------------------------------------------------------------
/routes/notes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router(); // Router is a method in express which helps in defining routes
3 | const Notes = require("../models/Note");
4 | let fetchuser = require("../middleware/fetchuser");
5 | const { body, validationResult } = require("express-validator"); // used in body of request
6 |
7 | //Route 1: fetch all notes of a user using GET
8 | // login required
9 | // fetchuser is a middleware which fetches the user id from jwt token
10 | // and appends it to req object
11 | // fetchuser is defined in middleware/fetchuser.js
12 |
13 | router.get("/fetchallnotes", fetchuser, async (req, res) => {
14 | try {
15 | //fetch all the notes linked with the user id
16 | const note = await Notes.find({ user: req.user.id });
17 | res.json(note); //return the notes
18 |
19 | } catch (error) {
20 | console.log(error.message);
21 | res.status(500).send("Internal server error has occured");
22 | }
23 | });
24 |
25 | //Route 2: Add a new note using POST
26 | router.post(
27 | "/addnote",
28 | fetchuser,
29 | [
30 | body("title", "Enter a valid title").isLength({ min: 3 }),
31 | body("description", "Enter a valid description").isLength({ min: 5 }),
32 | ],
33 | async (req, res) => {
34 | //if there are errors in request, return bad request and the errors
35 | // console.log(req);
36 | const error = validationResult(req);
37 | // console.log(error.array().length);
38 |
39 | if (error.array().length > 0) {
40 | return res.status(400).json({ error: error.array() });
41 | }
42 | // create a new note and save it to database
43 | try {
44 | const note = await Notes.create({
45 | user: req.user.id,
46 | title: req.body.title,
47 | description: req.body.description,
48 | tag: req.body.tag,
49 | date:Date.now()
50 | });
51 |
52 | res.send(note);
53 | } catch (error) {
54 | //catch error if any
55 | console.log(error.message);
56 | res.status(500).send("Internal server error has occured");
57 | }
58 | }
59 | );
60 |
61 | //Route 3: Update an existing note using PUT request using id of note to be updated and fetchuser middleware
62 | router.put("/updatenote/:id", fetchuser, async (req, res) => {
63 | try {
64 | let { title, description, tag } = req.body; // destructure the request body
65 |
66 | //create a new note object
67 | const newNote = {};
68 | if (title) {
69 | newNote.title = title;
70 | }
71 | if (description) {
72 | newNote.description = description;
73 | }
74 | if (tag) {
75 | newNote.tag = tag;
76 | }
77 | // newNote.date=Date.now();
78 | //find the note to be updated and update it
79 | //params is used to the parameters passed in the url
80 | let note = await Notes.findById(req.params.id);
81 | //check whether note exist or not
82 | if (!note) {
83 | return res.status(404).send("Not Found");
84 | }
85 | // allow updation only if login user same as requested user is same
86 | //is used for security purpose
87 | if (note.user.toString() !== req.user.id) {
88 | return res.status(404).send("Not Allowed");
89 | }
90 | //mongo query to find and update the note
91 | //new:true is used to return the updated note
92 | // $set is used to set the new note
93 | //req.params.id is used to find the note to be updated
94 | note = await Notes.findByIdAndUpdate(
95 | req.params.id,
96 | { $set: newNote },
97 | { new: true }
98 | );
99 | res.json({ note }); //return the updated note
100 | } catch (error) {
101 | console.log(error.message);
102 | res.status(500).send("Internal server error has occured");
103 | }
104 | });
105 |
106 | //Route 4: Delete an existing note using DELETE
107 | router.delete("/deletenote/:id", fetchuser, async (req, res) => {
108 | try {
109 | //find the note to be updated and delete it
110 | let note = await Notes.findById(req.params.id);
111 | if (!note) {
112 | return res.status(404).send("Not Found");
113 | }
114 | // allow deletion only if login user same as requested user is same
115 | if (note.user.toString() !== req.user.id) {
116 | return res.status(404).send("Not Allowed");
117 | }
118 | //mongo query to find and delete the note
119 | note = await Notes.findByIdAndDelete(req.params.id);
120 | res.json("Deleted");
121 | } catch (error) {
122 | console.log(error.message);
123 | res.status(500).send("Internal server error has occured");
124 | }
125 | });
126 |
127 | module.exports = router;
128 |
--------------------------------------------------------------------------------