├── frontend ├── public │ ├── _redirects │ ├── notebook.png │ ├── Images │ │ └── Profile_Pictures │ │ │ ├── profile1.jpg │ │ │ ├── profile2.jpg │ │ │ ├── profile3.jpg │ │ │ ├── profile4.jpg │ │ │ ├── profile5.jpg │ │ │ ├── profile6.jpg │ │ │ ├── profile7.jpg │ │ │ ├── profile8.jpg │ │ │ └── profile9.jpg │ ├── vite.svg │ └── notebook.svg ├── .env.sample ├── netlify.toml ├── postcss.config.js ├── src │ ├── lib │ │ └── utils.js │ ├── app │ │ └── store.js │ ├── main.jsx │ ├── pages │ │ ├── ErrorPage │ │ │ └── ErrorPage.jsx │ │ ├── About │ │ │ └── About.jsx │ │ ├── UsreProfileSettings │ │ │ └── UserProfileSettings.jsx │ │ ├── Login │ │ │ └── Login.jsx │ │ ├── TodoList │ │ │ └── TodoList.jsx │ │ ├── ContactUs │ │ │ └── ContactUs.jsx │ │ └── ForgotPassword │ │ │ └── ForgotPassword.jsx │ ├── features │ │ ├── user │ │ │ └── userSlice.js │ │ ├── notes │ │ │ └── notesSlice.js │ │ └── menu │ │ │ └── menuSlice.js │ ├── components │ │ ├── ui │ │ │ ├── toaster.jsx │ │ │ ├── popover.jsx │ │ │ ├── switch.jsx │ │ │ ├── button.jsx │ │ │ ├── use-toast.js │ │ │ ├── toast.jsx │ │ │ └── dropdown-menu.jsx │ │ ├── Loading │ │ │ └── Loading.jsx │ │ ├── DeleteNotes │ │ │ └── DeleteNotes.jsx │ │ ├── NotesView │ │ │ └── NotesView.jsx │ │ ├── NotesThemeColors │ │ │ └── NotesThemeColors.jsx │ │ ├── NotesLockBtn │ │ │ └── NotesLockBtn.jsx │ │ └── ChangeProfileImage │ │ │ └── ChangeProfileImage.jsx │ ├── styles │ │ ├── navbar │ │ │ └── navbar.module.css │ │ ├── loading │ │ │ └── loading.module.css │ │ ├── forgotPassword │ │ │ └── forgotPassword.module.css │ │ ├── signup │ │ │ └── signup.module.css │ │ └── login │ │ │ └── login.module.css │ ├── database │ │ └── picturesData.json │ ├── utils │ │ ├── userApiCall.js │ │ ├── apiRoutes.js │ │ └── notesApiCalling.js │ ├── index.css │ ├── App.css │ ├── App.jsx │ ├── assets │ │ └── react.svg │ └── common │ │ ├── Navbar │ │ └── Navbar.jsx │ │ └── SidebarMenu │ │ └── SidebarMenu.jsx ├── jsconfig.json ├── vite.config.js ├── .gitignore ├── index.html ├── components.json ├── README.md ├── .eslintrc.cjs ├── package.json └── tailwind.config.js ├── backend ├── .env.sample ├── src │ ├── utils │ │ ├── ApiResponse.js │ │ ├── hashPassword.js │ │ ├── asyncErrorHandler.js │ │ ├── errorHandler.js │ │ └── ApiError.js │ ├── db │ │ └── db.js │ ├── routes │ │ ├── notes.routes.js │ │ └── user.routes.js │ ├── index.js │ ├── schemas │ │ ├── login.schema.js │ │ ├── resetPassword.schema.js │ │ ├── signup.schema.js │ │ └── updateUser.schema.js │ ├── middlewares │ │ ├── validation.middleware.js │ │ └── auth.middleware.js │ ├── models │ │ ├── notes.model.js │ │ └── user.model.js │ ├── app.js │ └── controllers │ │ ├── notes.controller.js │ │ └── user.controller.js ├── package.json └── .gitignore └── README.md /frontend/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /frontend/.env.sample: -------------------------------------------------------------------------------- 1 | VITE_APP_CONTACT_URL= 2 | VITE_APP_API_KEY= -------------------------------------------------------------------------------- /backend/.env.sample: -------------------------------------------------------------------------------- 1 | PORT= 2 | MONGODB_URI= 3 | DB_NAME= 4 | JWT_SECRET_KEY= 5 | CORS_ORIGIN= -------------------------------------------------------------------------------- /frontend/netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 -------------------------------------------------------------------------------- /frontend/public/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/notebook.png -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile1.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile2.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile3.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile4.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile5.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile6.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile7.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile8.jpg -------------------------------------------------------------------------------- /frontend/public/Images/Profile_Pictures/profile9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabedalimollah/notebook/HEAD/frontend/public/Images/Profile_Pictures/profile9.jpg -------------------------------------------------------------------------------- /frontend/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // ... 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["./src/*"] 7 | } 8 | // ... 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import path from "path"; 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env 27 | -------------------------------------------------------------------------------- /backend/src/utils/ApiResponse.js: -------------------------------------------------------------------------------- 1 | class ApiResponse { 2 | constructor( 3 | status, 4 | data, 5 | token = null, 6 | statusInfo = "success", 7 | message = "success" 8 | ) { 9 | this.status = status; 10 | this.data = data; 11 | this.token = token; 12 | this.statusInfo = statusInfo; 13 | this.message = message; 14 | } 15 | } 16 | export default ApiResponse; 17 | -------------------------------------------------------------------------------- /frontend/src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import userReducer from "../features/user/userSlice"; 3 | import notesReducer from "../features/notes/notesSlice"; 4 | import menuReducer from "../features/menu/menuSlice"; 5 | export const store = configureStore({ 6 | reducer: { 7 | user: userReducer, 8 | notes: notesReducer, 9 | menu: menuReducer, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Notebook 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": false, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/utils/hashPassword.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcrypt"; 2 | 3 | const encryPassword = async (password) => { 4 | // if (!this.isModified("password")) { 5 | // return next(); 6 | // } 7 | try { 8 | const genSalt = await bcrypt.genSalt(10); 9 | const hashedPassword = await bcrypt.hash(password, genSalt); 10 | return (password = hashedPassword); 11 | } catch (error) { 12 | return next(error); 13 | } 14 | }; 15 | 16 | export { encryPassword }; 17 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /backend/src/db/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import dotenv from "dotenv"; 3 | dotenv.config(); 4 | 5 | const URI = process.env.MONGODB_URI; 6 | const DB_NAME = process.env.DB_NAME; 7 | 8 | // ------------- Database Connection ---------- 9 | const connectDB = async () => { 10 | try { 11 | await mongoose.connect(`${URI}/${DB_NAME}`); 12 | console.log("MongoDB connected"); 13 | } catch (err) { 14 | console.log("MongoDB connection faild !!!: ", err); 15 | } 16 | }; 17 | 18 | export default connectDB; 19 | -------------------------------------------------------------------------------- /backend/src/routes/notes.routes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | deleteData, 4 | findNotes, 5 | getData, 6 | postData, 7 | updateData, 8 | } from "../controllers/notes.controller.js"; 9 | const router = express.Router(); 10 | 11 | // =========== Router =========== 12 | router.post("/postdata", postData); 13 | router.get("/getdata/:id", getData); 14 | router.put("/updatedata/:id", updateData); 15 | router.delete("/deletedata/:id", deleteData); 16 | router.get("/findnotes/:id", findNotes); 17 | 18 | export default router; 19 | -------------------------------------------------------------------------------- /backend/src/index.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import connectDB from "./db/db.js"; 3 | import app from "./app.js"; 4 | dotenv.config(); 5 | 6 | // ------------ PORT ----------- 7 | const PORT = process.env.PORT || 8000; 8 | 9 | // --------- Database Connection --------- 10 | connectDB() 11 | .then(() => { 12 | app.listen(PORT, () => { 13 | console.log( 14 | `Server is running at port ${PORT}...\nhttp://localhost:${PORT}` 15 | ); 16 | }); 17 | }) 18 | .catch((err) => { 19 | console.log("MongoDB Connection Failed !!!", err); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/schemas/login.schema.js: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | const loginSchema = z.object({ 3 | // ------------------- email ----------------- 4 | email: z 5 | .string({ 6 | required_error: "email is required", 7 | invalid_type_error: "email must be a string", 8 | }) 9 | .email({ message: "Invalid email address" }), 10 | // ----------------- password ------------------ 11 | password: z.string({ 12 | required_error: "password is required", 13 | invalid_type_error: "password must be a string", 14 | }), 15 | }); 16 | export default loginSchema; 17 | -------------------------------------------------------------------------------- /backend/src/utils/asyncErrorHandler.js: -------------------------------------------------------------------------------- 1 | export default (requestHandler) => { 2 | return (req, res, next) => { 3 | requestHandler(req, res, next).catch((err) => next(err)); 4 | 5 | // ------------------- x ------------------ 6 | // try { 7 | // await requestHandler(req, res, next); 8 | // } catch (error) { 9 | // res.status(error.status).json({ 10 | // status: error.status || 500, 11 | // statusInfo: error.statusInfo || "Error", 12 | // response: error.response || "Internal Server Error", 13 | // }); 14 | // } 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import "regenerator-runtime/runtime"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom/client"; 4 | import App from "./App.jsx"; 5 | import "./index.css"; 6 | import { store } from "./app/store"; 7 | import { Provider } from "react-redux"; 8 | import { BrowserRouter } from "react-router-dom"; 9 | 10 | ReactDOM.createRoot(document.getElementById("root")).render( 11 | 12 | 13 | 14 | 15 | {" "} 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /backend/src/utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | const errorHandler = (err, req, res, next) => { 2 | // console.log("errorHandler", err); 3 | 4 | // err.status = err.status || 500; 5 | err.status = err.message ? (err.status ? err.status : 500) : 500; 6 | err.message = err.message || "Internal Server Error"; 7 | err.statusInfo = err.statusInfo || "error"; 8 | // err.data = err.data || null; 9 | res.status(err.status).json({ 10 | status: err.status, 11 | statusInfo: err.statusInfo, 12 | message: err.message, 13 | // data: err.data, 14 | data: null, 15 | }); 16 | }; 17 | export default errorHandler; 18 | -------------------------------------------------------------------------------- /frontend/src/pages/ErrorPage/ErrorPage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BiSolidError } from "react-icons/bi"; 3 | import { NavLink } from "react-router-dom"; 4 | const ErrorPage = () => { 5 | return ( 6 |
7 |
8 | 9 |

404 Page Not Found

10 | Back to Login Page 11 |
12 |
13 | ); 14 | }; 15 | 16 | export default ErrorPage; 17 | -------------------------------------------------------------------------------- /frontend/src/features/user/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | value: {}, 5 | }; 6 | 7 | export const userSlice = createSlice({ 8 | name: "user", 9 | initialState, 10 | reducers: { 11 | getData: (state, action) => { 12 | // console.log("action", action.payload); 13 | state.value = action.payload; 14 | }, 15 | }, 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { getData } = userSlice.actions; 20 | 21 | export default userSlice.reducer; 22 | 23 | // https://redux-toolkit.js.org/tutorials/quick-start 24 | -------------------------------------------------------------------------------- /frontend/src/features/notes/notesSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | value: [], 5 | }; 6 | 7 | export const userSlice = createSlice({ 8 | name: "notes", 9 | initialState, 10 | reducers: { 11 | allNotes: (state, action) => { 12 | // console.log("action", action.payload); 13 | state.value = action.payload; 14 | }, 15 | }, 16 | }); 17 | 18 | // Action creators are generated for each case reducer function 19 | export const { allNotes } = userSlice.actions; 20 | 21 | export default userSlice.reducer; 22 | 23 | // https://redux-toolkit.js.org/tutorials/quick-start 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "nodemon src/index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "description": "", 13 | "type": "module", 14 | "dependencies": { 15 | "bcrypt": "^5.1.1", 16 | "body-parser": "^1.20.2", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.4.5", 19 | "express": "^4.19.2", 20 | "jsonwebtoken": "^9.0.2", 21 | "mongoose": "^8.4.4", 22 | "zod": "^3.23.8" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^3.1.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/utils/ApiError.js: -------------------------------------------------------------------------------- 1 | export default class ApiError extends Error { 2 | constructor( 3 | status, 4 | statusInfo, 5 | message = "Something went wrong", 6 | stack = "" 7 | ) { 8 | super(message); 9 | 10 | // console.log("apiError", status, message); 11 | this.status = status || 500; 12 | // this.statusInfo = statusCode >= 400 && statusCode < 500 ? "fail" : "error"; 13 | this.statusInfo = statusInfo || "error"; 14 | this.message = message; 15 | this.success = false; 16 | this.data = null; 17 | 18 | if (stack) { 19 | this.stack = stack; 20 | } else { 21 | Error.captureStackTrace(this, this.constructor); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/features/menu/menuSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | value: false, 5 | }; 6 | 7 | export const menuSlice = createSlice({ 8 | name: "menu", 9 | initialState, 10 | reducers: { 11 | menuToggle: (state) => { 12 | // console.log("action", action.payload); 13 | // console.log(state.value); 14 | state.value = !state.value; 15 | // state.value = action.payload; 16 | }, 17 | }, 18 | }); 19 | 20 | // Action creators are generated for each case reducer function 21 | export const { menuToggle } = menuSlice.actions; 22 | 23 | export default menuSlice.reducer; 24 | 25 | // https://redux-toolkit.js.org/tutorials/quick-start 26 | -------------------------------------------------------------------------------- /backend/src/schemas/resetPassword.schema.js: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | // ==================== Reset Password Schema =============== 3 | const resetPasswordSchema = z.object({ 4 | // ------------------- email ----------------- 5 | email: z 6 | .string({ 7 | required_error: "email is required", 8 | invalid_type_error: "email must be a string", 9 | }) 10 | .email({ message: "Invalid email address" }), 11 | // ----------------- password ------------------ 12 | password: z 13 | .string({ 14 | required_error: "password is required", 15 | invalid_type_error: "password must be a string", 16 | }) 17 | .trim() 18 | .min(8, { message: "password must be at least 8 characters" }), 19 | }); 20 | 21 | export default resetPasswordSchema; 22 | -------------------------------------------------------------------------------- /frontend/src/components/ui/toaster.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/components/ui/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | ( 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | ( 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
) 30 | ); 31 | })} 32 | 33 |
) 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/styles/navbar/navbar.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100%; 3 | height: 40px; 4 | z-index: 1000; 5 | position: fixed; 6 | left: 0; 7 | top: 0; 8 | /* margin-bottom: 100px; */ 9 | } 10 | .main .header { 11 | width: 100%; 12 | height: 100%; 13 | background-color: #d6efd8; 14 | display: flex; 15 | align-items: center; 16 | } 17 | .logo_box { 18 | height: 90%; 19 | width: 135px; 20 | margin-left: 25px; 21 | } 22 | .logo { 23 | width: 100%; 24 | } 25 | 26 | /* ============== Tablets and Desktop ============= */ 27 | @media only screen and (min-width: 768px) and (max-width: 1024px) { 28 | /* tablets and desktop */ 29 | .logo_box { 30 | width: 100px; 31 | margin-left: 25px; 32 | height: auto; 33 | } 34 | } 35 | 36 | /* ============== Phone ================ */ 37 | @media only screen and (max-width: 767px) { 38 | /* phones */ 39 | .logo_box { 40 | height: auto; 41 | width: 100px; 42 | margin-left: 25px; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/src/middlewares/validation.middleware.js: -------------------------------------------------------------------------------- 1 | const userValidation = (schema) => async (req, res, next) => { 2 | try { 3 | const parseBody = await schema.parseAsync(req.body); 4 | req.body = parseBody; 5 | next(); 6 | } catch (err) { 7 | res.status(400).json({ 8 | status: 400, 9 | statusInfo: "fail", 10 | message: err.errors[0].message, 11 | data: null, 12 | }); 13 | // ------------------ 1st ---------------- 14 | // res.status(400).json({ stauts: 400, response: err.errors[0].message }); 15 | 16 | // ------------------ 2nd ---------------- 17 | // res.status(400).json({ 18 | // status: 400, 19 | // response: err.errors[0].response, 20 | // errorInfo: err.errors.map((curElm) => curElm.response), 21 | // }); 22 | 23 | // ------------------ 3rd ---------------- 24 | // next({ status: 400, statusInfo: "Error", response: err.errors[0].response }); 25 | } 26 | }; 27 | 28 | export { userValidation }; 29 | -------------------------------------------------------------------------------- /frontend/src/components/Loading/Loading.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "../../styles/loading/loading.module.css"; 3 | const Loading = () => { 4 | return ( 5 |
6 |
7 | {/* From Uiverse.io by doniaskima */} 8 |
9 | {/* 10 |
11 | 12 | 13 | 14 | 15 |
*/} 16 |
17 |
18 |
19 |
20 | {/*
21 |
*/} 22 |
23 |
24 |
25 | ); 26 | }; 27 | 28 | export default Loading; 29 | -------------------------------------------------------------------------------- /frontend/src/database/picturesData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "profile1", 5 | "path": "/Images/Profile_Pictures/profile1.jpg" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "profile2", 10 | "path": "/Images/Profile_Pictures/profile2.jpg" 11 | }, 12 | { 13 | "id": 3, 14 | "name": "profile3", 15 | "path": "/Images/Profile_Pictures/profile3.jpg" 16 | }, 17 | { 18 | "id": 4, 19 | "name": "profile4", 20 | "path": "/Images/Profile_Pictures/profile4.jpg" 21 | }, 22 | { 23 | "id": 5, 24 | "name": "profile5", 25 | "path": "/Images/Profile_Pictures/profile5.jpg" 26 | }, 27 | { 28 | "id": 6, 29 | "name": "profile6", 30 | "path": "/Images/Profile_Pictures/profile6.jpg" 31 | }, 32 | { 33 | "id": 7, 34 | "name": "profile7", 35 | "path": "/Images/Profile_Pictures/profile7.jpg" 36 | }, 37 | { 38 | "id": 8, 39 | "name": "profile8", 40 | "path": "/Images/Profile_Pictures/profile8.jpg" 41 | }, 42 | { 43 | "id": 9, 44 | "name": "profile9", 45 | "path": "/Images/Profile_Pictures/profile9.jpg" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /backend/src/models/notes.model.js: -------------------------------------------------------------------------------- 1 | import { mongoose, Schema } from "mongoose"; 2 | const notesSchema = new mongoose.Schema( 3 | { 4 | title: { 5 | type: String, 6 | default: "", 7 | }, 8 | text: { 9 | type: String, 10 | default: "", 11 | }, 12 | themeColor: { 13 | type: String, 14 | default: "#FFFFFF", 15 | }, 16 | fontFamily: { 17 | type: String, 18 | default: "Arial, Helvetica, sans-serif", 19 | }, 20 | fontStyle: { 21 | type: String, 22 | enum: ["normal", "italic"], 23 | default: "normal", 24 | }, 25 | fontColor: { 26 | type: String, 27 | default: "#000000", 28 | }, 29 | password: { 30 | type: String, 31 | }, 32 | isPasswordProtected: { 33 | type: Boolean, 34 | default: false, 35 | }, 36 | author: { 37 | type: Schema.Types.ObjectId, 38 | ref: "User", 39 | required: [true, "User id is Required"], 40 | }, 41 | }, 42 | { 43 | timestamps: true, 44 | } 45 | ); 46 | 47 | const Notes = mongoose.model("Notes", notesSchema); 48 | export { Notes }; 49 | -------------------------------------------------------------------------------- /frontend/src/components/ui/popover.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 13 | 14 | 23 | 24 | )) 25 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 26 | 27 | export { Popover, PopoverTrigger, PopoverContent } 28 | -------------------------------------------------------------------------------- /frontend/src/utils/userApiCall.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { jwtDecode } from "jwt-decode"; 3 | import { apiRoutes } from "./apiRoutes"; 4 | const token = localStorage.getItem("notebookToken"); 5 | const user = token ? jwtDecode(token) : null; // jwtDecode is for extract user id 6 | 7 | // ================= Get User Data ================= 8 | const GetUserData = async () => { 9 | try { 10 | const response = await axios.get( 11 | `${apiRoutes.userprofileURI}/${user._id}`, 12 | { headers: { Authorization: `Bearer ${token}` } } 13 | ); 14 | return await response.data.data; 15 | } catch (error) { 16 | console.log(error); 17 | } 18 | }; 19 | 20 | // ==================== Update User Data ======================= 21 | const UpdateUserData = async (updateData) => { 22 | try { 23 | const response = await axios.put( 24 | `${apiRoutes.updateUserProfileURI}/${user._id}`, 25 | updateData, 26 | { headers: { Authorization: `Bearer ${token}` } } 27 | ); 28 | // console.log(response.data.data) 29 | // dispatch(getData(response.data.data)); 30 | // setData(response.data.data); 31 | return await response.data.data; 32 | } catch (error) { 33 | console.log(error); 34 | } 35 | }; 36 | 37 | export { GetUserData, UpdateUserData }; 38 | -------------------------------------------------------------------------------- /frontend/src/components/ui/switch.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitives from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Switch = React.forwardRef(({ className, ...props }, ref) => ( 9 | 16 | 20 | 21 | )) 22 | Switch.displayName = SwitchPrimitives.Root.displayName 23 | 24 | export { Switch } 25 | -------------------------------------------------------------------------------- /backend/src/app.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import bodyParser from "body-parser"; 3 | import cors from "cors"; 4 | import ApiError from "./utils/ApiError.js"; 5 | import errorHandler from "./utils/errorHandler.js"; 6 | import { jwtAuthMiddleware } from "./middlewares/auth.middleware.js"; 7 | const app = express(); 8 | 9 | // ----------- Middlewares ---------- 10 | app.use( 11 | cors({ 12 | origin: process.env.CORS_ORIGIN, 13 | credentials: true, 14 | }) 15 | ); 16 | app.use(bodyParser.json()); 17 | 18 | // --------- Import Routes ------------- 19 | import user from "./routes/user.routes.js"; 20 | import notes from "./routes/notes.routes.js"; 21 | 22 | // ----------- Routes declaration --------- 23 | app.use("/api/v1/user", user); 24 | app.use("/api/v1/notes", jwtAuthMiddleware, notes); 25 | 26 | // ----------- It is used for incorrect endpoint and wrong api requests ---------- 27 | app.use("*", (req, res, next) => { 28 | // =============== x ================== 29 | // const err = new Error(`Can't find ${req.originalUrl} on the server`); 30 | // err.status = "fail"; 31 | // err.statusCode = 404; 32 | const err = new ApiError( 33 | 404, 34 | "fail", 35 | `Can't find ${req.originalUrl} on the server` 36 | ); 37 | next(err); 38 | }); 39 | 40 | // ----------------- Error handler --------- 41 | app.use(errorHandler); 42 | 43 | // --------- Export ---------- 44 | export default app; 45 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/src/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | deleteUserProfile, 4 | getUserProfile, 5 | login, 6 | resetPassword, 7 | signup, 8 | updateUserProfile, 9 | } from "../controllers/user.controller.js"; 10 | const router = express.Router(); 11 | import { userValidation } from "../middlewares/validation.middleware.js"; 12 | import { signUpSchema } from "../schemas/signup.schema.js"; 13 | import loginSchema from "../schemas/login.schema.js"; 14 | import { jwtAuthMiddleware } from "../middlewares/auth.middleware.js"; 15 | import resetPasswordSchema from "../schemas/resetPassword.schema.js"; 16 | import updateUserSchema from "../schemas/updateUser.schema.js"; 17 | 18 | // =============== Router =============== 19 | 20 | // --------------- signup -------------- 21 | router.post("/signup", userValidation(signUpSchema), signup); 22 | 23 | // --------------- log in ------------- 24 | router.post("/login", userValidation(loginSchema), login); 25 | 26 | // ------------- Reset Password --------- 27 | router.put( 28 | "/resetpassword", 29 | userValidation(resetPasswordSchema), 30 | resetPassword 31 | ); 32 | 33 | // ------------- Update User Profile ---------- 34 | router.put( 35 | "/updateuser/:id", 36 | jwtAuthMiddleware, 37 | userValidation(updateUserSchema), 38 | updateUserProfile 39 | ); 40 | 41 | // ---------------- Get User Profile ------------ 42 | router.get("/userprofile/:id", jwtAuthMiddleware, getUserProfile); 43 | 44 | // ---------------- Delete User Profile ------------ 45 | router.delete("/deleteuser/:id", jwtAuthMiddleware, deleteUserProfile); 46 | 47 | // --------------- Export Router -------------- 48 | export default router; 49 | -------------------------------------------------------------------------------- /frontend/src/utils/apiRoutes.js: -------------------------------------------------------------------------------- 1 | const apiRoutes = { 2 | signupURI: `${import.meta.env.VITE_APP_API_KEY}/user/signup`, 3 | loginURI: `${import.meta.env.VITE_APP_API_KEY}/user/login`, 4 | resetpasswordURI: `${import.meta.env.VITE_APP_API_KEY}/user/resetpassword`, 5 | userprofileURI: `${import.meta.env.VITE_APP_API_KEY}/user/userprofile`, 6 | updateUserProfileURI: `${import.meta.env.VITE_APP_API_KEY}/user/updateuser`, 7 | deleteUserProfileURI: `${import.meta.env.VITE_APP_API_KEY}/user/deleteuser`, 8 | 9 | createNotesURI: `${import.meta.env.VITE_APP_API_KEY}/notes/postdata`, 10 | getNotesURI: `${import.meta.env.VITE_APP_API_KEY}/notes/getdata`, 11 | findNotesURI: `${import.meta.env.VITE_APP_API_KEY}/notes/findnotes`, 12 | updateNotesURI: `${import.meta.env.VITE_APP_API_KEY}/notes/updatedata`, 13 | deleteNotesURI: `${import.meta.env.VITE_APP_API_KEY}/notes/deletedata`, 14 | }; 15 | 16 | // const apiRoutes = { 17 | // signupURI: "http://localhost:8000/api/v1/user/signup", 18 | // loginURI: "http://localhost:8000/api/v1/user/login", 19 | // resetpasswordURI: "http://localhost:8000/api/v1/user/resetpassword", 20 | // userprofileURI: "http://localhost:8000/api/v1/user/userprofile", 21 | // updateUserProfileURI: "http://localhost:8000/api/v1/user/updateuser", 22 | // deleteUserProfileURI: "http://localhost:8000/api/v1/user/deleteuser", 23 | 24 | // createNotesURI: "http://localhost:8000/api/v1/notes/postdata", 25 | // getNotesURI: "http://localhost:8000/api/v1/notes/getdata", 26 | // findNotesURI: "http://localhost:8000/api/v1/notes/findnotes", 27 | // updateNotesURI: "http://localhost:8000/api/v1/notes/updatedata", 28 | // deleteNotesURI: "http://localhost:8000/api/v1/notes/deletedata", 29 | // }; 30 | 31 | export { apiRoutes }; 32 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 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 | "@radix-ui/react-dropdown-menu": "^2.1.1", 14 | "@radix-ui/react-popover": "^1.1.1", 15 | "@radix-ui/react-slot": "^1.1.0", 16 | "@radix-ui/react-switch": "^1.1.0", 17 | "@reduxjs/toolkit": "^2.2.6", 18 | "axios": "^1.7.2", 19 | "class-variance-authority": "^0.7.0", 20 | "clsx": "^2.1.1", 21 | "dotenv": "^16.4.5", 22 | "jspdf": "^2.5.1", 23 | "jwt-decode": "^4.0.0", 24 | "lucide-react": "^0.407.0", 25 | "pdf-lib": "^1.17.1", 26 | "react": "^19.0.0-rc-df783f9ea1-20240708", 27 | "react-dom": "^19.0.0-rc-df783f9ea1-20240708", 28 | "react-icons": "^5.2.1", 29 | "react-modal": "^3.16.1", 30 | "react-redux": "^9.1.2", 31 | "react-router-dom": "^6.24.1", 32 | "react-speech-kit": "^3.0.1", 33 | "react-speech-recognition": "^3.10.0", 34 | "react-toastify": "^10.0.5", 35 | "react-use-clipboard": "^1.0.9", 36 | "regenerator-runtime": "^0.14.1", 37 | "tailwind-merge": "^2.4.0", 38 | "tailwindcss-animate": "^1.0.7" 39 | }, 40 | "devDependencies": { 41 | "@types/react": "^18.3.3", 42 | "@types/react-dom": "^18.3.0", 43 | "@vitejs/plugin-react": "^4.3.1", 44 | "autoprefixer": "^10.4.19", 45 | "eslint": "^8.57.0", 46 | "eslint-plugin-react": "^7.34.2", 47 | "eslint-plugin-react-hooks": "^4.6.2", 48 | "eslint-plugin-react-refresh": "^0.4.7", 49 | "postcss": "^8.4.39", 50 | "tailwindcss": "^3.4.4", 51 | "vite": "^5.3.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/components/ui/button.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => { 37 | const Comp = asChild ? Slot : "button" 38 | return ( 39 | () 43 | ); 44 | }) 45 | Button.displayName = "Button" 46 | 47 | export { Button, buttonVariants } 48 | -------------------------------------------------------------------------------- /frontend/src/components/DeleteNotes/DeleteNotes.jsx: -------------------------------------------------------------------------------- 1 | import { DeleteNotesData, GetNotes } from "@/utils/notesApiCalling"; 2 | import React, { useState } from "react"; 3 | import Loading from "../Loading/Loading"; 4 | 5 | const DeleteNotes = ({ handleDeleteBtn, notesId }) => { 6 | const [loading, setLoading] = useState(false); 7 | const handleDeleteData = async () => { 8 | setLoading(true); 9 | let res = await DeleteNotesData(notesId); 10 | // console.log(res); 11 | 12 | if (res === "success") { 13 | // GetNotes(); 14 | handleDeleteBtn(); 15 | setLoading(false); 16 | } 17 | }; 18 | return ( 19 | <> 20 | {loading ? : ""} 21 |
22 |
23 |

Delete Notes

24 |

25 | Are you sure you want to delete this notes ? 26 |

27 |
28 | 34 | 40 |
41 |
{" "} 42 |
43 | 44 | ); 45 | }; 46 | 47 | export default DeleteNotes; 48 | -------------------------------------------------------------------------------- /backend/src/middlewares/auth.middleware.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { User } from "../models/user.model.js"; 3 | import ApiError from "../utils/ApiError.js"; 4 | import asyncErrorHandler from "../utils/asyncErrorHandler.js"; 5 | 6 | // --------------- Create Token ---------------- 7 | const generateToken = (userData) => { 8 | return jwt.sign(userData, process.env.JWT_SECRET_KEY); 9 | }; 10 | 11 | // ---------------- Verify Token ------------- 12 | const jwtAuthMiddleware = asyncErrorHandler(async (req, res, next) => { 13 | const authorization = req.headers.authorization; 14 | 15 | if (!authorization) { 16 | // throw { 17 | // status: 404, 18 | // statusInfo: "fail", 19 | // response: "Token not found", 20 | // }; 21 | throw new ApiError(404, "fail", "Token not found"); 22 | 23 | // return res.status(404).json({ 24 | // status: 404, 25 | // statusInfo: "fail", 26 | // response: "Token not found", 27 | // }); 28 | } 29 | const token = req.headers.authorization.split(" ")[1]; 30 | if (!token) { 31 | // throw { 32 | // status: 401, 33 | // statusInfo: "fail", 34 | // response: "Unauthorized request", 35 | // }; 36 | throw new ApiError(401, "fail", "Unauthorized request"); 37 | } 38 | try { 39 | const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY); 40 | const user = await User.findById(decoded._id); 41 | 42 | if (!user) { 43 | // throw { 44 | // status: 401, 45 | // statusInfo: "fail", 46 | // response: "Invalid Access Token", 47 | // }; 48 | throw new ApiError(401, "fail", "Invalid Access Token"); 49 | } 50 | 51 | req.user = decoded; 52 | next(); 53 | } catch (error) { 54 | // throw { 55 | // status: 401, 56 | // statusInfo: "fail", 57 | // response: "Invalid Access Token", 58 | // }; 59 | throw new ApiError(401, "fail", "Invalid Access Token"); 60 | } 61 | }); 62 | export { generateToken, jwtAuthMiddleware }; 63 | -------------------------------------------------------------------------------- /backend/src/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import bcrypt from "bcrypt"; 3 | const userSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | }, 8 | username: { 9 | type: String, 10 | required: true, 11 | unique: true, 12 | lowercase: true, 13 | }, 14 | email: { 15 | type: String, 16 | required: true, 17 | unique: true, 18 | lowercase: true, 19 | }, 20 | phoneNumber: { 21 | type: String, 22 | }, 23 | password: { 24 | type: String, 25 | required: true, 26 | }, 27 | profilePic: { 28 | type: String, 29 | default: "/Images/Profile_Pictures/profile1.jpg", 30 | }, 31 | gender: { 32 | type: String, 33 | }, 34 | dateOfBirth: { 35 | type: String, 36 | }, 37 | country: { 38 | type: String, 39 | default: "India", 40 | }, 41 | state: { 42 | type: String, 43 | }, 44 | themeColor: { 45 | type: String, 46 | default: "bg-green-100", 47 | }, 48 | fontFamily: { 49 | type: String, 50 | default: "Arial, Helvetica, sans-serif", 51 | }, 52 | fontStyle: { 53 | type: String, 54 | enum: ["normal", "italic"], 55 | default: "normal", 56 | }, 57 | fontColor: { 58 | type: String, 59 | default: "#000000", 60 | }, 61 | gridView: { 62 | type: String, 63 | default: "grid", 64 | }, 65 | }); 66 | // ------------------- Hashed Password -------------------- 67 | userSchema.pre("save", async function (next) { 68 | // --------- check password is modified or not ----------- 69 | if (!this.isModified("password")) { 70 | return next(); 71 | } 72 | try { 73 | const genSalt = await bcrypt.genSalt(10); 74 | const hashedPassword = await bcrypt.hash(this.password, genSalt); 75 | this.password = hashedPassword; 76 | } catch (error) { 77 | return next(error); 78 | } 79 | }); 80 | 81 | userSchema.methods.comparePassword = async function (password) { 82 | return await bcrypt.compare(password, this.password); 83 | }; 84 | 85 | const User = mongoose.model("User", userSchema); 86 | export { User }; 87 | -------------------------------------------------------------------------------- /backend/src/schemas/signup.schema.js: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | const signUpSchema = z.object({ 3 | // --------------- name ------------- 4 | name: z 5 | .string({ 6 | required_error: "name is required", 7 | invalid_type_error: "name must be a string", 8 | }) 9 | .trim() 10 | .min(2, { message: "name must be at least 2 characters" }) 11 | .max(20, { message: "name must be at maximum 20 characters" }) 12 | .regex( 13 | /^[A-Za-z.-]+(\s*[A-Za-z.-]+)*$/, 14 | "name must not contain special characters and numbers" 15 | ), 16 | 17 | // --------------- username ------------- 18 | username: z 19 | .string({ 20 | required_error: "username is required", 21 | invalid_type_error: "username must be a string", 22 | }) 23 | .trim() 24 | .min(2, { message: "username must be at least 2 characters" }) 25 | .max(20, { message: "username must be at maximum 20 characters" }) 26 | .toLowerCase() 27 | .regex( 28 | /^[a-zA-Z0-9]{2,20}$/, 29 | "username must not contain special characters" 30 | ), 31 | 32 | // --------------- email ------------- 33 | email: z 34 | .string({ 35 | required_error: "email is required", 36 | invalid_type_error: "email must be a string", 37 | }) 38 | .email({ message: "Invalid email address" }) 39 | .trim() 40 | .toLowerCase(), 41 | 42 | // --------------- phone number ------------- 43 | phoneNumber: z 44 | .string({ 45 | required_error: "phone number is required", 46 | invalid_type_error: "phone number must be a string", 47 | }) 48 | .min(6, { message: "phone number must be at least 6 numbers" }) 49 | .max(12, { 50 | message: "phone number max 12 numbers", 51 | }) 52 | .optional(), 53 | 54 | // --------------- password ------------- 55 | password: z 56 | .string({ 57 | required_error: "password is required", 58 | invalid_type_error: "password must be a string", 59 | }) 60 | .trim() 61 | .min(8, { message: "password must be at least 8 characters" }), 62 | 63 | // ------------------ Grid View ------------- 64 | gridView: z.string().optional(), 65 | }); 66 | 67 | export { signUpSchema }; 68 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 222.2 84% 4.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 222.2 84% 4.9%; 13 | --primary: 222.2 47.4% 11.2%; 14 | --primary-foreground: 210 40% 98%; 15 | --secondary: 210 40% 96.1%; 16 | --secondary-foreground: 222.2 47.4% 11.2%; 17 | --muted: 210 40% 96.1%; 18 | --muted-foreground: 215.4 16.3% 46.9%; 19 | --accent: 210 40% 96.1%; 20 | --accent-foreground: 222.2 47.4% 11.2%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 210 40% 98%; 23 | --border: 214.3 31.8% 91.4%; 24 | --input: 214.3 31.8% 91.4%; 25 | --ring: 222.2 84% 4.9%; 26 | --radius: 0.5rem; 27 | --chart-1: 12 76% 61%; 28 | --chart-2: 173 58% 39%; 29 | --chart-3: 197 37% 24%; 30 | --chart-4: 43 74% 66%; 31 | --chart-5: 27 87% 67%; 32 | } 33 | 34 | .dark { 35 | --background: 222.2 84% 4.9%; 36 | --foreground: 210 40% 98%; 37 | --card: 222.2 84% 4.9%; 38 | --card-foreground: 210 40% 98%; 39 | --popover: 222.2 84% 4.9%; 40 | --popover-foreground: 210 40% 98%; 41 | --primary: 210 40% 98%; 42 | --primary-foreground: 222.2 47.4% 11.2%; 43 | --secondary: 217.2 32.6% 17.5%; 44 | --secondary-foreground: 210 40% 98%; 45 | --muted: 217.2 32.6% 17.5%; 46 | --muted-foreground: 215 20.2% 65.1%; 47 | --accent: 217.2 32.6% 17.5%; 48 | --accent-foreground: 210 40% 98%; 49 | --destructive: 0 62.8% 30.6%; 50 | --destructive-foreground: 210 40% 98%; 51 | --border: 217.2 32.6% 17.5%; 52 | --input: 217.2 32.6% 17.5%; 53 | --ring: 212.7 26.8% 83.9%; 54 | --chart-1: 220 70% 50%; 55 | --chart-2: 160 60% 45%; 56 | --chart-3: 30 80% 55%; 57 | --chart-4: 280 65% 60%; 58 | --chart-5: 340 75% 55%; 59 | } 60 | } 61 | 62 | @layer base { 63 | * { 64 | @apply border-border; 65 | } 66 | body { 67 | @apply bg-background text-foreground; 68 | } 69 | } 70 | 71 | * { 72 | box-sizing: border-box; 73 | margin: 0; 74 | padding: 0; 75 | } 76 | body { 77 | font-family: Arial, Helvetica, sans-serif; 78 | } 79 | a { 80 | text-decoration: none; 81 | } 82 | p { 83 | margin-bottom: 0 !important; 84 | } 85 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .background_color { 2 | background-color: #eaf7dd; 3 | } 4 | 5 | .box_color { 6 | background: #d6efd8; 7 | } 8 | 9 | .box_shadow { 10 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 11 | } 12 | 13 | .button_gradient_color { 14 | /* background: rgb(217, 237, 219); 15 | background: linear-gradient( 16 | 65deg, 17 | rgba(217, 237, 219, 1) 61%, 18 | rgba(238, 255, 245, 1) 61% 19 | ); */ 20 | background: rgb(255, 0, 78); 21 | background: linear-gradient( 22 | 33deg, 23 | rgba(255, 0, 78, 1) 53%, 24 | rgba(249, 250, 119, 1) 91% 25 | ); 26 | } 27 | .background_gradient_color { 28 | background: rgb(217, 237, 219); 29 | background: linear-gradient( 30 | 65deg, 31 | rgba(217, 237, 219, 1) 61%, 32 | rgba(238, 255, 245, 1) 61% 33 | ); 34 | } 35 | /* ------------- This is for Navbar active links ------------ */ 36 | a.active { 37 | background-color: rgb(22 163 74); 38 | color: white; 39 | } 40 | 41 | /* -------------------- This is For Notebook line Style -------------- */ 42 | .textContent { 43 | margin-top: 5px; 44 | width: 96%; 45 | height: 87%; 46 | border: none; 47 | outline: none; 48 | margin-bottom: 5px; 49 | border-radius: 0 0 20px 20px; 50 | padding: 8px 10px; 51 | /* background-attachment: local; */ 52 | background-image: linear-gradient(to right, white 10px, transparent 10px), 53 | linear-gradient(to left, white 10px, transparent 10px), 54 | repeating-linear-gradient( 55 | white, 56 | white 30px, 57 | #ccc 32px, 58 | #ccc 31px, 59 | white 31px 60 | ); 61 | line-height: 31px; 62 | } 63 | 64 | textarea::-webkit-scrollbar { 65 | width: 0.5em; 66 | cursor: pointer; 67 | } 68 | 69 | textarea::-webkit-scrollbar-track { 70 | box-shadow: inset 0 0 6px hwb(0 0% 100% / 0); 71 | cursor: pointer; 72 | } 73 | 74 | textarea::-webkit-scrollbar-thumb { 75 | background-color: rgba(86, 88, 89, 0.467); 76 | cursor: pointer; 77 | /* outline: 1px solid slategrey; */ 78 | } 79 | 80 | /* -------------- This is for text-overflow will add three dots at the end of the line ---------------- */ 81 | .truncate { 82 | text-overflow: ellipsis; 83 | overflow: hidden; 84 | white-space: nowrap; 85 | } 86 | 87 | .textContentHide { 88 | margin-top: 5px; 89 | width: 96%; 90 | height: 87%; 91 | border: none; 92 | outline: none; 93 | margin-bottom: 5px; 94 | border-radius: 0 0 20px 20px; 95 | padding: 8px 10px; 96 | line-height: 31px; 97 | } 98 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{js,jsx}', 6 | './components/**/*.{js,jsx}', 7 | './app/**/*.{js,jsx}', 8 | './src/**/*.{js,jsx}', 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | colors: { 21 | border: "hsl(var(--border))", 22 | input: "hsl(var(--input))", 23 | ring: "hsl(var(--ring))", 24 | background: "hsl(var(--background))", 25 | foreground: "hsl(var(--foreground))", 26 | primary: { 27 | DEFAULT: "hsl(var(--primary))", 28 | foreground: "hsl(var(--primary-foreground))", 29 | }, 30 | secondary: { 31 | DEFAULT: "hsl(var(--secondary))", 32 | foreground: "hsl(var(--secondary-foreground))", 33 | }, 34 | destructive: { 35 | DEFAULT: "hsl(var(--destructive))", 36 | foreground: "hsl(var(--destructive-foreground))", 37 | }, 38 | muted: { 39 | DEFAULT: "hsl(var(--muted))", 40 | foreground: "hsl(var(--muted-foreground))", 41 | }, 42 | accent: { 43 | DEFAULT: "hsl(var(--accent))", 44 | foreground: "hsl(var(--accent-foreground))", 45 | }, 46 | popover: { 47 | DEFAULT: "hsl(var(--popover))", 48 | foreground: "hsl(var(--popover-foreground))", 49 | }, 50 | card: { 51 | DEFAULT: "hsl(var(--card))", 52 | foreground: "hsl(var(--card-foreground))", 53 | }, 54 | }, 55 | borderRadius: { 56 | lg: "var(--radius)", 57 | md: "calc(var(--radius) - 2px)", 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | "accordion-down": { 62 | from: { height: "0" }, 63 | to: { height: "var(--radix-accordion-content-height)" }, 64 | }, 65 | "accordion-up": { 66 | from: { height: "var(--radix-accordion-content-height)" }, 67 | to: { height: "0" }, 68 | }, 69 | }, 70 | animation: { 71 | "accordion-down": "accordion-down 0.2s ease-out", 72 | "accordion-up": "accordion-up 0.2s ease-out", 73 | }, 74 | }, 75 | }, 76 | plugins: [require("tailwindcss-animate")], 77 | } -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | 121 | 122 | # End of https://mrkandreev.name/snippets/gitignore-generator/#Node -------------------------------------------------------------------------------- /frontend/src/utils/notesApiCalling.js: -------------------------------------------------------------------------------- 1 | import { apiRoutes } from "./apiRoutes"; 2 | import axios from "axios"; 3 | import { jwtDecode } from "jwt-decode"; 4 | const token = localStorage.getItem("notebookToken"); 5 | const user = token ? jwtDecode(token) : null; // jwtDecode is for extract user id 6 | 7 | // ==================== Post Notes [create new notes] =========================== 8 | const PostNotes = async (data) => { 9 | try { 10 | // const token = localStorage.getItem("notebookToken"); 11 | const response = await axios.post(apiRoutes.createNotesURI, data, { 12 | headers: { Authorization: `Bearer ${token}` }, 13 | }); 14 | // console.log(response.data); 15 | } catch (error) { 16 | // console.log(error); 17 | } 18 | }; 19 | 20 | // ============================ Get All Notes ================================== 21 | const GetNotes = async () => { 22 | try { 23 | const response = await axios.get(`${apiRoutes.getNotesURI}/${user._id}`, { 24 | headers: { Authorization: `Bearer ${token}` }, 25 | }); 26 | // console.log(await response.data.data); 27 | return await response.data.data; 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | 33 | // ====================== Find One Notes ================================== 34 | const FindNotes = async (notesId) => { 35 | try { 36 | const response = await axios.get(`${apiRoutes.findNotesURI}/${notesId}`, { 37 | headers: { Authorization: `Bearer ${token}` }, 38 | }); 39 | // console.log(response.data.data); 40 | return await response.data.data; 41 | } catch (error) { 42 | console.log(error); 43 | } 44 | }; 45 | 46 | // ========================== Update Exist Notes ============================= 47 | const UpdateNotesData = async (notesId, newData) => { 48 | try { 49 | const response = await axios.put( 50 | `${apiRoutes.updateNotesURI}/${notesId}`, 51 | newData, 52 | { 53 | headers: { Authorization: `Bearer ${token}` }, 54 | } 55 | ); 56 | // console.log(response.data.data); 57 | return await response.data.data; 58 | } catch (error) { 59 | console.log(error); 60 | } 61 | }; 62 | 63 | // ========================== Delete Exist Notes ======================== 64 | const DeleteNotesData = async (notesId) => { 65 | try { 66 | const response = await axios.delete( 67 | `${apiRoutes.deleteNotesURI}/${notesId}`, 68 | { 69 | headers: { Authorization: `Bearer ${token}` }, 70 | } 71 | ); 72 | // console.log(response); 73 | return await response.data.statusInfo; 74 | } catch (error) { 75 | console.log(error); 76 | } 77 | }; 78 | 79 | export { PostNotes, GetNotes, FindNotes, UpdateNotesData, DeleteNotesData }; 80 | -------------------------------------------------------------------------------- /backend/src/controllers/notes.controller.js: -------------------------------------------------------------------------------- 1 | import { Notes } from "../models/notes.model.js"; 2 | import ApiError from "../utils/ApiError.js"; 3 | // import ApiResponse from "../utils/apiResponse.js"; 4 | import ApiResponse from "../utils/ApiResponse.js"; 5 | import asyncErrorHandler from "../utils/asyncErrorHandler.js"; 6 | 7 | // ================ Post Data ========= 8 | const postData = asyncErrorHandler(async (req, res) => { 9 | const data = req.body; 10 | if (!data.author) { 11 | throw new ApiError(404, "fail", "author not found"); 12 | } 13 | if (data.password) { 14 | data.isPasswordProtected = true; 15 | } 16 | const newData = new Notes(data); 17 | const newNotes = await newData.save(); 18 | res 19 | .status(200) 20 | .json( 21 | new ApiResponse( 22 | 200, 23 | newNotes, 24 | null, 25 | "success", 26 | "notes created successfully" 27 | ) 28 | ); 29 | }); 30 | 31 | // ================ Get Data ============= 32 | const getData = asyncErrorHandler(async (req, res) => { 33 | const { id } = req.params; 34 | const data = await Notes.find({ author: id }); 35 | res 36 | .status(200) 37 | .json( 38 | new ApiResponse(200, data, null, "success", "data getting successfully") 39 | ); 40 | }); 41 | 42 | // ============== Update Data ============ 43 | const updateData = asyncErrorHandler(async (req, res) => { 44 | const { id } = req.params; 45 | const data = req.body; 46 | 47 | const updateData = await Notes.findByIdAndUpdate(id, data, { 48 | new: true, 49 | runValidators: true, 50 | }); 51 | if (!updateData) { 52 | throw new ApiError(401, "fail", "notes not found"); 53 | } 54 | 55 | res 56 | .status(200) 57 | .json( 58 | new ApiResponse( 59 | 200, 60 | updateData, 61 | null, 62 | "success", 63 | "data updated successfully" 64 | ) 65 | ); 66 | }); 67 | 68 | // =============== Delete Data =============== 69 | const deleteData = asyncErrorHandler(async (req, res) => { 70 | const { id } = req.params; 71 | const deleteData = await Notes.findByIdAndDelete(id); 72 | if (!deleteData) { 73 | throw new ApiError(404, "fail", "data not found"); 74 | } 75 | res 76 | .status(200) 77 | .json( 78 | new ApiResponse(200, null, null, "success", "data deleted successfully") 79 | ); 80 | }); 81 | 82 | // =============== Find Notes ============= 83 | const findNotes = asyncErrorHandler(async (req, res) => { 84 | const { id } = req.params; 85 | const data = await Notes.findById(id); 86 | if (!data) { 87 | throw new ApiError(404, "fail", "data not found"); 88 | } 89 | res 90 | .status(200) 91 | .json(new ApiResponse(200, data, null, "success", "data get successfully")); 92 | }); 93 | 94 | // ============ Export ========== 95 | export { postData, getData, updateData, deleteData, findNotes }; 96 | -------------------------------------------------------------------------------- /frontend/src/components/NotesView/NotesView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { TfiLayoutGrid3Alt } from "react-icons/tfi"; 3 | import { TfiLayoutGrid2Alt } from "react-icons/tfi"; 4 | import { ImMenu } from "react-icons/im"; 5 | import { RiCloseLargeFill } from "react-icons/ri"; 6 | import { UpdateUserData } from "@/utils/userApiCall"; 7 | import Loading from "../Loading/Loading"; 8 | import { useDispatch, useSelector } from "react-redux"; 9 | import { getData } from "@/features/user/userSlice"; 10 | 11 | const NotesView = ({ handleGridBtn }) => { 12 | const [loading, setLoading] = useState(false); 13 | const user = useSelector((state) => state.user.value); 14 | const dispatch = useDispatch(); 15 | const handleChangeGrid = async (grid) => { 16 | // console.log(gridValue); 17 | setLoading(true); 18 | let newD = await UpdateUserData({ gridView: grid }); 19 | setLoading(false); 20 | dispatch(getData(newD)); 21 | 22 | handleGridBtn(newD); 23 | }; 24 | const backBtn = () => { 25 | // const response = await GetUserData(); 26 | handleGridBtn(user); 27 | }; 28 | useEffect(() => {}); 29 | return ( 30 | <> 31 | {loading ? : ""} 32 |
33 |
34 | {/* ================== Close Button =============== */} 35 | 41 |

View

42 | 49 | 55 | 62 |
63 |
64 | 65 | ); 66 | }; 67 | 68 | export default NotesView; 69 | -------------------------------------------------------------------------------- /frontend/src/components/NotesThemeColors/NotesThemeColors.jsx: -------------------------------------------------------------------------------- 1 | import { GetUserData, UpdateUserData } from "@/utils/userApiCall"; 2 | import React, { useState } from "react"; 3 | import { IoCloseSharp } from "react-icons/io5"; 4 | import Loading from "../Loading/Loading"; 5 | import { useDispatch } from "react-redux"; 6 | import { getData } from "@/features/user/userSlice"; 7 | const NotesThemeColors = ({ handleThemeColor, handleThemeBackBtn }) => { 8 | const [color, setColor] = useState(""); 9 | const [loading, setLoading] = useState(false); 10 | const dispatch = useDispatch(); 11 | const handleNotesThemeColorBtn = async (thmColor) => { 12 | setLoading(true); 13 | let response = await UpdateUserData({ themeColor: thmColor }); 14 | setLoading(false); 15 | // console.log(thmColor); 16 | dispatch(getData(response)); 17 | handleThemeColor(response); 18 | }; 19 | return ( 20 | <> 21 | {loading ? : ""} 22 |
23 |
24 |
25 | 31 |

Choose Color

32 |
33 |
34 | 38 | 39 | 43 | 44 | 48 | 49 | 53 | 54 | 58 | 59 | 63 |
64 |
65 |
66 | 67 | ); 68 | }; 69 | 70 | export default NotesThemeColors; 71 | -------------------------------------------------------------------------------- /backend/src/schemas/updateUser.schema.js: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | const updateUserSchema = z.object({ 4 | // --------------- name ------------- 5 | name: z 6 | .string({ 7 | invalid_type_error: "name must be a string", 8 | }) 9 | .trim() 10 | .min(2, { message: "name must be at least 2 characters" }) 11 | .max(20, { message: "name must be at maximum 20 characters" }) 12 | .regex( 13 | /^[A-Za-z.-]+(\s*[A-Za-z.-]+)*$/, 14 | "name must not contain special characters and numbers" 15 | ) 16 | .optional(), 17 | 18 | // --------------- username ------------- 19 | username: z 20 | .string({ 21 | invalid_type_error: "username must be a string", 22 | }) 23 | .trim() 24 | .min(2, { message: "username must be at least 2 characters" }) 25 | .max(20, { message: "username must be at maximum 20 characters" }) 26 | .toLowerCase() 27 | // .regex( 28 | // /^[a-zA-Z0-9]{2,20}$/, 29 | // "username must not contain special characters" 30 | // ) 31 | .optional(), 32 | 33 | // --------------- email ------------- 34 | email: z 35 | .string({ 36 | invalid_type_error: "email must be a string", 37 | }) 38 | .email({ message: "Invalid email address" }) 39 | .trim() 40 | .toLowerCase() 41 | .optional(), 42 | 43 | // --------------- phone number ------------- 44 | phoneNumber: z 45 | .string({ 46 | required_error: "phone number is required", 47 | invalid_type_error: "phone number must be a string", 48 | }) 49 | .min(6, { message: "phone number must be at least 6 numbers" }) 50 | .max(12, { 51 | message: "phone number max 12 numbers", 52 | }) 53 | .optional(), 54 | 55 | // ------------------ Profile Picture ---------------- 56 | profilePic: z.string().optional(), 57 | 58 | // --------------------- Gender --------------------- 59 | gender: z.string({ invalid_type_error: "them must be a string" }).optional(), 60 | 61 | // ------------------ Date of Birth -------------- 62 | dateOfBirth: z 63 | .string({ invalid_type_error: "them must be a string" }) 64 | .optional(), 65 | 66 | // --------------------- Country ------------------- 67 | country: z.string({ invalid_type_error: "them must be a string" }).optional(), 68 | 69 | // ------------------------- State ----------------- 70 | state: z.string({ invalid_type_error: "them must be a string" }).optional(), 71 | 72 | // --------------- Theme Color ------------- 73 | themeColor: z 74 | .string({ invalid_type_error: "them must be a string" }) 75 | .optional(), 76 | 77 | // ----------------- font family ---------- 78 | fontFamily: z 79 | .string({ invalid_type_error: "font family must be a string" }) 80 | .optional(), 81 | 82 | // --------------- font style ---------- 83 | fontStyle: z.enum(["normal", "italic"]).optional(), 84 | 85 | // -------------- font color ------------- 86 | fontColor: z 87 | .string({ invalid_type_error: "font color must be a string" }) 88 | .optional(), 89 | // ----------------------- Grid View ----------------- 90 | gridView: z.string().optional(), 91 | }); 92 | 93 | export default updateUserSchema; 94 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import reactLogo from "./assets/react.svg"; 3 | import viteLogo from "/vite.svg"; 4 | import "./App.css"; 5 | import { Button } from "@/components/ui/button"; 6 | import { BrowserRouter, Route, Routes, useNavigate } from "react-router-dom"; 7 | import Signup from "./pages/Signup/Signup"; 8 | import Navbar from "./common/Navbar/Navbar"; 9 | import Login from "./pages/Login/Login"; 10 | import ForgotPassword from "./pages/ForgotPassword/ForgotPassword"; 11 | import UserProfile from "./pages/UserProfile/UserProfile"; 12 | import EditProfile from "./pages/EditProfile/EditProfile"; 13 | import UserProfileSettings from "./pages/UsreProfileSettings/UserProfileSettings"; 14 | import UserNotes from "./pages/UserNotes/UserNotes"; 15 | import TodoList from "./pages/TodoList/TodoList"; 16 | import About from "./pages/About/About"; 17 | import ContactUs from "./pages/ContactUs/ContactUs"; 18 | import CreateUpdateNotes from "./components/CreateUpdateNotes/CreateUpdateNotes"; 19 | import "react-toastify/dist/ReactToastify.css"; 20 | import ErrorPage from "./pages/ErrorPage/ErrorPage"; 21 | // const login = localStorage.getItem("notebookToken"); 22 | function App() { 23 | const [auth, setAuth] = useState(null); 24 | const navigate = useNavigate(); 25 | useEffect(() => { 26 | const login = localStorage.getItem("notebookToken"); 27 | // const login = JSON.parse(localStorage.getItem("items")); 28 | 29 | if (login) { 30 | // const login1 = localStorage.getItem("notebookToken"); 31 | navigate("/user/notes"); 32 | // console.log(login); 33 | setAuth(login); 34 | } else { 35 | navigate("/user/login"); 36 | } 37 | }, []); 38 | return ( 39 | <> 40 | {/* */} 41 | 42 | 43 | {auth ? ( 44 | <> 45 | } /> 46 | 47 | } /> 48 | } /> 49 | } /> 50 | 51 | } /> 52 | } /> 53 | } /> 54 | } /> 55 | } /> 56 | } 59 | /> 60 | } /> 61 | 62 | ) : ( 63 | <> 64 | {/* } /> */} 65 | } /> 66 | } /> 67 | 68 | )} 69 | } /> 70 | 71 | {/* */} 72 | 73 | ); 74 | } 75 | 76 | export default App; 77 | -------------------------------------------------------------------------------- /frontend/src/components/ui/use-toast.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | // Inspired by react-hot-toast library 3 | import * as React from "react" 4 | 5 | const TOAST_LIMIT = 1 6 | const TOAST_REMOVE_DELAY = 1000000 7 | 8 | const actionTypes = { 9 | ADD_TOAST: "ADD_TOAST", 10 | UPDATE_TOAST: "UPDATE_TOAST", 11 | DISMISS_TOAST: "DISMISS_TOAST", 12 | REMOVE_TOAST: "REMOVE_TOAST" 13 | } 14 | 15 | let count = 0 16 | 17 | function genId() { 18 | count = (count + 1) % Number.MAX_SAFE_INTEGER 19 | return count.toString(); 20 | } 21 | 22 | const toastTimeouts = new Map() 23 | 24 | const addToRemoveQueue = (toastId) => { 25 | if (toastTimeouts.has(toastId)) { 26 | return 27 | } 28 | 29 | const timeout = setTimeout(() => { 30 | toastTimeouts.delete(toastId) 31 | dispatch({ 32 | type: "REMOVE_TOAST", 33 | toastId: toastId, 34 | }) 35 | }, TOAST_REMOVE_DELAY) 36 | 37 | toastTimeouts.set(toastId, timeout) 38 | } 39 | 40 | export const reducer = (state, action) => { 41 | switch (action.type) { 42 | case "ADD_TOAST": 43 | return { 44 | ...state, 45 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 46 | }; 47 | 48 | case "UPDATE_TOAST": 49 | return { 50 | ...state, 51 | toasts: state.toasts.map((t) => 52 | t.id === action.toast.id ? { ...t, ...action.toast } : t), 53 | }; 54 | 55 | case "DISMISS_TOAST": { 56 | const { toastId } = action 57 | 58 | // ! Side effects ! - This could be extracted into a dismissToast() action, 59 | // but I'll keep it here for simplicity 60 | if (toastId) { 61 | addToRemoveQueue(toastId) 62 | } else { 63 | state.toasts.forEach((toast) => { 64 | addToRemoveQueue(toast.id) 65 | }) 66 | } 67 | 68 | return { 69 | ...state, 70 | toasts: state.toasts.map((t) => 71 | t.id === toastId || toastId === undefined 72 | ? { 73 | ...t, 74 | open: false, 75 | } 76 | : t), 77 | }; 78 | } 79 | case "REMOVE_TOAST": 80 | if (action.toastId === undefined) { 81 | return { 82 | ...state, 83 | toasts: [], 84 | } 85 | } 86 | return { 87 | ...state, 88 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 89 | }; 90 | } 91 | } 92 | 93 | const listeners = [] 94 | 95 | let memoryState = { toasts: [] } 96 | 97 | function dispatch(action) { 98 | memoryState = reducer(memoryState, action) 99 | listeners.forEach((listener) => { 100 | listener(memoryState) 101 | }) 102 | } 103 | 104 | function toast({ 105 | ...props 106 | }) { 107 | const id = genId() 108 | 109 | const update = (props) => 110 | dispatch({ 111 | type: "UPDATE_TOAST", 112 | toast: { ...props, id }, 113 | }) 114 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) 115 | 116 | dispatch({ 117 | type: "ADD_TOAST", 118 | toast: { 119 | ...props, 120 | id, 121 | open: true, 122 | onOpenChange: (open) => { 123 | if (!open) dismiss() 124 | }, 125 | }, 126 | }) 127 | 128 | return { 129 | id: id, 130 | dismiss, 131 | update, 132 | } 133 | } 134 | 135 | function useToast() { 136 | const [state, setState] = React.useState(memoryState) 137 | 138 | React.useEffect(() => { 139 | listeners.push(setState) 140 | return () => { 141 | const index = listeners.indexOf(setState) 142 | if (index > -1) { 143 | listeners.splice(index, 1) 144 | } 145 | }; 146 | }, [state]) 147 | 148 | return { 149 | ...state, 150 | toast, 151 | dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId }), 152 | }; 153 | } 154 | 155 | export { useToast, toast } 156 | -------------------------------------------------------------------------------- /frontend/src/styles/loading/loading.module.css: -------------------------------------------------------------------------------- 1 | /* From Uiverse.io by doniaskima */ 2 | .loader { 3 | width: -moz-fit-content; 4 | width: fit-content; 5 | 6 | font-weight: bold; 7 | font-family: monospace; 8 | font-size: 30px; 9 | /* font-size: 20px; */ 10 | /* background: radial-gradient(circle closest-side, #000 94%, #0000) */ 11 | background: radial-gradient(circle closest-side, #033e1b 94%, #0000) 12 | right/calc(200% - 1em) 100%; 13 | animation: l24 1s infinite alternate linear; 14 | } 15 | 16 | .loader::before { 17 | content: "Loading..."; 18 | /* content: "Loading..."; */ 19 | line-height: 1em; 20 | color: #0000; 21 | background: inherit; 22 | background-image: radial-gradient(circle closest-side, #fff 94%, #093b09); 23 | /* background-image: radial-gradient(circle closest-side, #fff 94%, #000); */ 24 | -webkit-background-clip: text; 25 | background-clip: text; 26 | } 27 | 28 | @keyframes l24 { 29 | 100% { 30 | background-position: left; 31 | } 32 | } 33 | 34 | /* =========================== 2nd ==================== */ 35 | /* From Uiverse.io by Ali-Tahmazi99 */ 36 | .load_row { 37 | width: 100px; 38 | height: 50px; 39 | line-height: 50px; 40 | text-align: center; 41 | } 42 | 43 | .load_row span { 44 | display: inline-block; 45 | width: 10px; 46 | height: 10px; 47 | background: #f76002; 48 | border-radius: 50px; 49 | animation: up-down6 0.5s ease-in infinite alternate; 50 | } 51 | 52 | .load_row span:nth-child(2) { 53 | background: #e85b04c4; 54 | animation-delay: 0.16s; 55 | } 56 | 57 | .load_row span:nth-child(3) { 58 | background: #e85b0491; 59 | animation-delay: 0.32s; 60 | } 61 | 62 | .load_row span:nth-child(4) { 63 | background: #e85b0456; 64 | animation-delay: 0.48s; 65 | } 66 | 67 | @keyframes up-down6 { 68 | 0% { 69 | transform: translateY(-10px); 70 | } 71 | 72 | 100% { 73 | transform: translateY(10px); 74 | } 75 | } 76 | /* =========================== 2nd ==================== */ 77 | 78 | /* ======================== 3rd ======================== */ 79 | /* From Uiverse.io by Satwinder04 */ 80 | .loader3 { 81 | display: flex; 82 | justify-content: center; 83 | align-items: center; 84 | } 85 | 86 | .circle1 { 87 | width: 20px; 88 | height: 20px; 89 | border-radius: 50%; 90 | margin: 0 10px; 91 | background-color: #154326; 92 | animation: circle1 1s ease-in-out infinite; 93 | } 94 | 95 | .circle1:nth-child(2) { 96 | animation-delay: 0.2s; 97 | } 98 | 99 | .circle1:nth-child(3) { 100 | animation-delay: 0.4s; 101 | } 102 | 103 | .circle1:nth-child(4) { 104 | animation-delay: 0.6s; 105 | } 106 | 107 | .circle1:nth-child(5) { 108 | animation-delay: 0.8s; 109 | } 110 | 111 | @keyframes circle1 { 112 | 0% { 113 | transform: scale(1); 114 | opacity: 1; 115 | } 116 | 117 | 50% { 118 | transform: scale(1.5); 119 | opacity: 0.5; 120 | } 121 | 122 | 100% { 123 | transform: scale(1); 124 | opacity: 1; 125 | } 126 | } 127 | 128 | /* ======================== 3rd ======================== */ 129 | 130 | /* ============== Phone ================ */ 131 | @media only screen and (max-width: 767px) { 132 | /* phones */ 133 | .loader { 134 | width: -moz-fit-content; 135 | width: fit-content; 136 | 137 | font-weight: bold; 138 | font-family: monospace; 139 | /* font-size: 30px; */ 140 | font-size: 20px; 141 | /* background: radial-gradient(circle closest-side, #000 94%, #0000) */ 142 | background: radial-gradient(circle closest-side, #033e1b 94%, #0000) 143 | right/calc(200% - 1em) 100%; 144 | animation: l24 1s infinite alternate linear; 145 | } 146 | .circle1 { 147 | width: 10px; 148 | height: 10px; 149 | border-radius: 50%; 150 | margin: 0 10px; 151 | background-color: #154326; 152 | animation: circle1 1s ease-in-out infinite; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/ui/toast.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import * as React from "react" 3 | import * as ToastPrimitives from "@radix-ui/react-toast" 4 | import { cva } from "class-variance-authority"; 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const ToastProvider = ToastPrimitives.Provider 10 | 11 | const ToastViewport = React.forwardRef(({ className, ...props }, ref) => ( 12 | 19 | )) 20 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 21 | 22 | const toastVariants = cva( 23 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border border-slate-200 p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-slate-800", 24 | { 25 | variants: { 26 | variant: { 27 | default: "border bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50", 28 | destructive: 29 | "destructive group border-red-500 bg-red-500 text-slate-50 dark:border-red-900 dark:bg-red-900 dark:text-slate-50", 30 | }, 31 | }, 32 | defaultVariants: { 33 | variant: "default", 34 | }, 35 | } 36 | ) 37 | 38 | const Toast = React.forwardRef(({ className, variant, ...props }, ref) => { 39 | return ( 40 | () 44 | ); 45 | }) 46 | Toast.displayName = ToastPrimitives.Root.displayName 47 | 48 | const ToastAction = React.forwardRef(({ className, ...props }, ref) => ( 49 | 56 | )) 57 | ToastAction.displayName = ToastPrimitives.Action.displayName 58 | 59 | const ToastClose = React.forwardRef(({ className, ...props }, ref) => ( 60 | 68 | 69 | 70 | )) 71 | ToastClose.displayName = ToastPrimitives.Close.displayName 72 | 73 | const ToastTitle = React.forwardRef(({ className, ...props }, ref) => ( 74 | 75 | )) 76 | ToastTitle.displayName = ToastPrimitives.Title.displayName 77 | 78 | const ToastDescription = React.forwardRef(({ className, ...props }, ref) => ( 79 | 80 | )) 81 | ToastDescription.displayName = ToastPrimitives.Description.displayName 82 | 83 | export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction }; 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📓 Notebook - Secure Note-Taking Platform 2 | 3 | Notebook is a secure, full-featured note-taking platform built with the MERN stack. It offers robust features for managing, securing, and organizing your notes, making it an ideal tool for both personal and professional use. 4 | 5 | 6 | ## 🔗 Live Demo 7 | 8 | Check out the live demo of the Notebook project [here](https://notebook7.netlify.app/). 9 | 10 | 11 | ## 🚀 Features 12 | 13 | - Secure User Authentication: Register and login with secure authentication to protect your notes. 14 | - Note Management: Easily create, update, and delete notes. 15 | - Export Options: Export your notes as PDF or TXT files for sharing or backup. 16 | - Clipboard Copy: Copy notes directly to your clipboard with a single click. 17 | - File Import: Import notes seamlessly from TXT files. 18 | - Advanced Search & Sorting: Quickly find and organize notes with powerful search and sorting tools. 19 | - Checklist Functionality: Create and manage tasks within your notes using checklists. 20 | 21 | 22 | ## 🛠️ Technologies Used 23 | 24 | **Client:** React, Redux, TailwindCSS, Axios 25 | 26 | **Server:** Node, Express 27 | 28 | **Database:** MongoDB 29 | 30 | 31 | ## 🖥️ Installation 32 | 33 | ### Prerequisites 34 | - Node.js and npm installed on your machine. 35 | - MongoDB installed and running locally or have access to a MongoDB Atlas cluster. 36 | 37 | 38 | ### Clone the Repository 39 | ```bash 40 | git clone https://github.com/jabedalimollah/notebook.git 41 | cd notebook 42 | ``` 43 | ### Backend Setup 44 | - Navigate to the backend directory: 45 | 46 | ```bash 47 | cd backend 48 | ``` 49 | - Install the required dependencies: 50 | ```bash 51 | npm install 52 | ``` 53 | - Create a .env file in the backend directory and add your environment variables: 54 | ```bash 55 | MONGO_URI= 56 | JWT_SECRET= 57 | PORT=5000 58 | DB_NAME= 59 | CORS_ORIGIN= 60 | ``` 61 | - Start the backend server 62 | ```bash 63 | npm run dev 64 | ``` 65 | 66 | ### Frontend Setup 67 | - Navigate to the frontend directory: 68 | 69 | ```bash 70 | cd frontend 71 | ``` 72 | - Install the required dependencies: 73 | 74 | ```bash 75 | npm install 76 | ``` 77 | - Create a .env file in the frontend directory and add your environment variables: 78 | ```bash 79 | VITE_APP_CONTACT_URL= 80 | VITE_APP_API_KEY=http://localhost:5000/api 81 | ``` 82 | - Start the frontend development server: 83 | ```bash 84 | npm run dev 85 | ``` 86 | 87 | ### Running the Application 88 | - Ensure both the frontend and backend servers are running. 89 | - Open your browser and navigate to http://localhost:5173. 90 | ## Environment Variables 91 | 92 | To run this project, you will need to add the following environment variables to your .env file 93 | ### Frontend 94 | 95 | `VITE_APP_CONTACT_URL` 96 | 97 | `VITE_APP_API_KEY` 98 | 99 | ### Backend 100 | 101 | `PORT` 102 | 103 | `MONGODB_URI` 104 | 105 | `DB_NAME` 106 | 107 | `JWT_SECRET_KEY` 108 | 109 | `CORS_ORIGIN` 110 | 111 | 112 | ![Logo](https://github.com/jabedalimollah/notebook/blob/d2eb6e6e0c363afdf0d4aa2ac688d296af7f3a9a/frontend/public/notebook.png?raw=true) 113 | 114 | 115 | ## Screenshots 116 | 117 | ![App Screenshot](https://media.licdn.com/dms/image/v2/D5622AQEb7iH27v0FZA/feedshare-shrink_800/feedshare-shrink_800/0/1723743354251?e=2147483647&v=beta&t=fWQgzRzGRMW2KoqFS6o6ShwLNuD3ZZmVzGF7-aGfRKc) 118 | 119 | ![App Screenshot](https://media.licdn.com/dms/image/v2/D5622AQHipsjZyfty6w/feedshare-shrink_800/feedshare-shrink_800/0/1723743350107?e=2147483647&v=beta&t=Zfp0AeVH0It7jp5bvbkeSUh49awq5IgYDbgj_G4Wi00) 120 | 121 | ![App Screenshot](https://media.licdn.com/dms/image/v2/D5622AQFlv6Wcn9HQoA/feedshare-shrink_800/feedshare-shrink_800/0/1723743357484?e=2147483647&v=beta&t=Xv9HzG0u3SMjGjWjjITRJ3vQWQNe_EsXYvexlUp5dO0) 122 | 123 | 124 | 125 | 126 | ## 📧 Contact 127 | 128 | - Email: jabedalimollah7@gmail.com 129 | - LinkedIn: [Jabed Ali Mollah](https://www.linkedin.com/in/jabedalimollah) 130 | - GitHub: [Jabed Ali Mollah](https://github.com/jabedalimollah) 131 | 132 | # 133 | 134 | If you find this project useful, don't forget to star the repository! ⭐ 135 | 136 | # 137 | 138 | 139 | ## 🔗 Links 140 | [![portfolio](https://img.shields.io/badge/my_portfolio-000?style=for-the-badge&logo=ko-fi&logoColor=white)](https://jabedalimollah.netlify.app/) 141 | 142 | [![linkedin](https://img.shields.io/badge/linkedin-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/jabedalimollah/) 143 | 144 | [![twitter](https://img.shields.io/badge/twitter-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white)](https://x.com/JabedAliMollah7) 145 | 146 | -------------------------------------------------------------------------------- /frontend/src/styles/forgotPassword/forgotPassword.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100%; 3 | height: 100vh; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-color: #eaf7dd; 8 | } 9 | .password_reset_container { 10 | width: 30%; 11 | /* height: 500px; */ 12 | /* box-shadow: 4px 5px 10px black; */ 13 | border-radius: 10px; 14 | padding: 25px; 15 | /* background-color: #d9eddb; */ 16 | 17 | background: rgb(217, 237, 219); 18 | background: linear-gradient( 19 | 65deg, 20 | rgba(217, 237, 219, 1) 61%, 21 | rgba(238, 255, 245, 1) 61% 22 | ); 23 | 24 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 25 | } 26 | .logo_box { 27 | /* width: 130px; */ 28 | width: 100px; 29 | } 30 | .form_title { 31 | /* text-align: center; */ 32 | font-size: 1.4rem; 33 | font-weight: bold; 34 | color: #1b561b; 35 | margin-bottom: 9px; 36 | } 37 | .password_reset_form { 38 | width: 100%; 39 | height: 100%; 40 | display: flex; 41 | flex-direction: column; 42 | font-weight: bold; 43 | /* color: #133b13; */ 44 | color: #1b561b; 45 | font-size: 0.8rem; 46 | } 47 | .div_wrapper { 48 | width: 100%; 49 | display: flex; 50 | margin: 4px 0; 51 | } 52 | .message { 53 | /* border: 2px solid black; */ 54 | width: 100%; 55 | padding: 5px; 56 | text-align: center; 57 | /* color: #ae0404; */ 58 | border-radius: 2px; 59 | /* background-color: #ff9393; */ 60 | color: #ffffff; 61 | background-color: #c20202; 62 | } 63 | .form_input_box { 64 | width: 100%; 65 | /* margin: 0px 6px; */ 66 | 67 | /* border: 2px solid black; */ 68 | } 69 | .form_data_wrapper { 70 | width: 100%; 71 | } 72 | .input_box { 73 | width: 100%; 74 | padding: 6px; 75 | outline: none; 76 | /* border: 1px solid #133113; */ 77 | border-radius: 5px; 78 | /* background-color: #ffffff; */ 79 | /* box-shadow: 0px 1px 4px green inset; */ 80 | margin: 4px 0px; 81 | /* color: white; */ 82 | background-color: white; 83 | /* background-color: #eaf7dd; */ 84 | border: 1px solid #75ab40; 85 | } 86 | .input_box:focus { 87 | border: 2px solid #5fba5f; 88 | } 89 | .invalid_user { 90 | color: rgb(170, 28, 28); 91 | font-weight: normal; 92 | } 93 | .password_reset_box { 94 | width: 100%; 95 | margin: 8px 6px; 96 | /* display: flex; 97 | justify-content: center; */ 98 | } 99 | .password_reset_button { 100 | width: 100%; 101 | /* background-color: #1f5d1f; */ 102 | background-color: #217f21; 103 | color: white; 104 | border-radius: 5px; 105 | padding: 6px; 106 | margin-top: 12px; 107 | } 108 | .password_reset_button:hover { 109 | background-color: #1f5d1f; 110 | } 111 | .login { 112 | width: 100%; 113 | margin-top: 10px; 114 | text-align: center; 115 | /* font-weight: normal; */ 116 | font-size: 0.8rem; 117 | } 118 | .login_wrapper { 119 | /* width: 96%; */ 120 | color: #133b13; 121 | } 122 | .login { 123 | color: #197819; 124 | } 125 | .forgot_password_box { 126 | width: 100%; 127 | display: flex; 128 | justify-content: space-between; 129 | } 130 | .remember_me { 131 | display: flex; 132 | color: #133b13; 133 | } 134 | .remember_me_text { 135 | margin-left: 6px; 136 | } 137 | .forgot_password_text { 138 | color: #197819; 139 | } 140 | .password_box { 141 | display: flex; 142 | justify-content: space-between; 143 | width: 100%; 144 | padding: 6px; 145 | outline: none; 146 | /* border: 1px solid #133113; */ 147 | border-radius: 5px; 148 | /* background-color: #ffffff; */ 149 | /* box-shadow: 0px 1px 4px green inset; */ 150 | margin: 4px 0px; 151 | /* color: white; */ 152 | background-color: white; 153 | /* background-color: #eaf7dd; */ 154 | border: 1px solid #75ab40; 155 | } 156 | .password_input_box { 157 | width: 90%; 158 | background-color: transparent; 159 | outline: none; 160 | border: none; 161 | } 162 | .eye_botton { 163 | /* border: 2px solid black; */ 164 | color: #919591; 165 | } 166 | 167 | /* ============== Tablets and Desktop ============= */ 168 | @media only screen and (min-width: 768px) and (max-width: 1024px) { 169 | /* tablets and desktop */ 170 | .password_reset_container { 171 | width: 50%; 172 | /* height: 500px; */ 173 | /* box-shadow: 4px 5px 10px black; */ 174 | border-radius: 10px; 175 | padding: 25px; 176 | /* background-color: #d9eddb; */ 177 | background: rgb(217, 237, 219); 178 | background: linear-gradient( 179 | 65deg, 180 | rgba(217, 237, 219, 1) 61%, 181 | rgba(238, 255, 245, 1) 61% 182 | ); 183 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 184 | } 185 | } 186 | 187 | /* ============== Phone ================ */ 188 | @media only screen and (max-width: 767px) { 189 | /* phones */ 190 | .main { 191 | width: 100%; 192 | height: 100vh; 193 | display: flex; 194 | justify-content: center; 195 | align-items: flex-start; 196 | background-color: #eaf7dd; 197 | } 198 | .password_reset_container { 199 | width: 80%; 200 | margin-top: 50px; 201 | /* height: 500px; */ 202 | /* box-shadow: 4px 5px 10px black; */ 203 | border-radius: 10px; 204 | padding: 25px; 205 | /* background-color: #d9eddb; */ 206 | background: rgb(217, 237, 219); 207 | background: linear-gradient( 208 | 65deg, 209 | rgba(217, 237, 219, 1) 61%, 210 | rgba(238, 255, 245, 1) 61% 211 | ); 212 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 213 | } 214 | .form_title { 215 | /* text-align: center; */ 216 | font-size: 1.1rem; 217 | font-weight: bold; 218 | color: #1b561b; 219 | margin-bottom: 9px; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /frontend/src/styles/signup/signup.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100%; 3 | height: 100vh; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-color: #eaf7dd; 8 | } 9 | .signup_container { 10 | width: 30%; 11 | /* height: 500px; */ 12 | /* box-shadow: 4px 5px 10px black; */ 13 | border-radius: 10px; 14 | padding: 25px; 15 | /* background-color: #d9eddb; */ 16 | 17 | background: rgb(217, 237, 219); 18 | background: linear-gradient( 19 | 65deg, 20 | rgba(217, 237, 219, 1) 61%, 21 | rgba(238, 255, 245, 1) 61% 22 | ); 23 | 24 | /* background: rgb(217, 237, 219); 25 | background: linear-gradient( 26 | 65deg, 27 | rgba(217, 237, 219, 1) 61%, 28 | rgba(238, 255, 245, 1) 77% 29 | ); */ 30 | 31 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 32 | } 33 | .logo_box { 34 | /* width: 130px; */ 35 | width: 100px; 36 | } 37 | .form_title { 38 | /* text-align: center; */ 39 | font-size: 1.4rem; 40 | font-weight: bold; 41 | color: #1b561b; 42 | margin-bottom: 9px; 43 | } 44 | .signup_form { 45 | width: 100%; 46 | height: 100%; 47 | display: flex; 48 | flex-direction: column; 49 | font-weight: bold; 50 | /* color: #133b13; */ 51 | color: #1b561b; 52 | font-size: 0.8rem; 53 | } 54 | .div_wrapper { 55 | width: 100%; 56 | display: flex; 57 | margin: 4px 0; 58 | justify-content: space-between; 59 | } 60 | .form_input_box { 61 | /* width: 46%; */ 62 | width: 49%; 63 | /* margin: 0px 6px; */ 64 | /* border: 2px solid black; */ 65 | } 66 | .form_data_wrapper { 67 | width: 100%; 68 | } 69 | .input_box { 70 | width: 100%; 71 | padding: 6px; 72 | outline: none; 73 | /* border: 1px solid #133113; */ 74 | border-radius: 5px; 75 | /* background-color: #ffffff; */ 76 | /* box-shadow: 0px 1px 4px green inset; */ 77 | margin: 4px 0px; 78 | /* color: white; */ 79 | background-color: white; 80 | /* background-color: #eaf7dd; */ 81 | 82 | border: 1px solid #75ab40; 83 | } 84 | .valid_input { 85 | border: 2px solid #75ab40; 86 | } 87 | /* .valid_input:focus { 88 | border: 2px solid green; 89 | } */ 90 | .invalid_input { 91 | border: 2px solid #ab4040; 92 | } 93 | /* .invalid_input:focus { 94 | border: 2px solid red; 95 | } */ 96 | .input_box:focus { 97 | border: 2px solid #5fba5f; 98 | } 99 | .password_box { 100 | display: flex; 101 | justify-content: space-between; 102 | width: 100%; 103 | padding: 6px; 104 | outline: none; 105 | /* border: 1px solid #133113; */ 106 | border-radius: 5px; 107 | /* background-color: #ffffff; */ 108 | /* box-shadow: 0px 1px 4px green inset; */ 109 | margin: 4px 0px; 110 | /* color: white; */ 111 | background-color: white; 112 | /* background-color: #eaf7dd; */ 113 | border: 1px solid #75ab40; 114 | } 115 | .password_input_box { 116 | width: 90%; 117 | background-color: transparent; 118 | outline: none; 119 | border: none; 120 | } 121 | .eye_botton { 122 | /* border: 2px solid black; */ 123 | color: #919591; 124 | } 125 | .invalid_user { 126 | color: rgb(170, 28, 28); 127 | font-weight: normal; 128 | } 129 | .signup_box { 130 | width: 100%; 131 | /* margin: 8px 6px; */ 132 | margin-top: 8px; 133 | display: flex; 134 | justify-content: center; 135 | } 136 | .signup_button { 137 | width: 100%; 138 | /* background-color: #1f5d1f; */ 139 | background-color: #217f21; 140 | color: white; 141 | border-radius: 5px; 142 | padding: 6px; 143 | } 144 | .signup_button:hover { 145 | background-color: #1f5d1f; 146 | } 147 | .login { 148 | width: 100%; 149 | margin-top: 10px; 150 | text-align: center; 151 | /* font-weight: normal; */ 152 | font-size: 0.8rem; 153 | } 154 | .login_wrapper { 155 | /* width: 96%; */ 156 | color: #133b13; 157 | } 158 | .login { 159 | color: #197819; 160 | } 161 | 162 | /* ============== Tablets and Desktop ============= */ 163 | @media only screen and (min-width: 768px) and (max-width: 1024px) { 164 | /* tablets and desktop */ 165 | .signup_container { 166 | width: 60%; 167 | /* height: 500px; */ 168 | /* box-shadow: 4px 5px 10px black; */ 169 | border-radius: 10px; 170 | padding: 25px; 171 | /* background-color: #d9eddb; */ 172 | background: rgb(217, 237, 219); 173 | background: linear-gradient( 174 | 65deg, 175 | rgba(217, 237, 219, 1) 61%, 176 | rgba(238, 255, 245, 1) 61% 177 | ); 178 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 179 | } 180 | } 181 | 182 | /* ============== Phone ================ */ 183 | @media only screen and (max-width: 767px) { 184 | /* phones */ 185 | .main { 186 | width: 100%; 187 | height: 100vh; 188 | display: flex; 189 | justify-content: center; 190 | align-items: flex-start; 191 | background-color: #eaf7dd; 192 | } 193 | .signup_container { 194 | width: 80%; 195 | height: auto; 196 | margin-top: 45px; 197 | /* height: 500px; */ 198 | /* box-shadow: 4px 5px 10px black; */ 199 | border-radius: 10px; 200 | padding: 25px; 201 | /* background-color: #d9eddb; */ 202 | background: rgb(217, 237, 219); 203 | background: linear-gradient( 204 | 65deg, 205 | rgba(217, 237, 219, 1) 61%, 206 | rgba(238, 255, 245, 1) 61% 207 | ); 208 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 209 | } 210 | .div_wrapper { 211 | width: 100%; 212 | display: flex; 213 | flex-direction: column; 214 | margin: 0px; 215 | justify-content: space-between; 216 | } 217 | .form_input_box { 218 | /* width: 46%; */ 219 | width: 100%; 220 | margin: 3px 0px; 221 | /* margin: 0px 6px; */ 222 | /* border: 2px solid black; */ 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /frontend/src/components/NotesLockBtn/NotesLockBtn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | // import { Label } from "../ui/"; 3 | import { Switch } from "../ui/switch"; 4 | import { BiSolidLock } from "react-icons/bi"; 5 | import { BiSolidLockOpen } from "react-icons/bi"; 6 | import { useParams } from "react-router-dom"; 7 | const NotesLockBtn = ({ handlelockBtn, handleNotesPasswordReturn, data }) => { 8 | // ------------------ State Start ---------------- 9 | const [switchChecked, setSwitchChecked] = useState(""); 10 | const [passwordDetails, setPasswordDetails] = useState({ 11 | isPasswordProtected: false, 12 | password: "", 13 | confirmNotesPassword: "", 14 | }); 15 | 16 | // ----------------- State End --------------- 17 | // ----------------- Get params Data ---------- 18 | const { notes_id } = useParams(); // get params data [ notes id ] 19 | 20 | const handleNotesPasswordInput = (e) => { 21 | // console.log(switchChecked); 22 | setPasswordDetails({ 23 | ...passwordDetails, 24 | 25 | [e.target.name]: e.target.value, 26 | }); 27 | // setPasswordDetails({ 28 | // ...passwordDetails, 29 | // isPasswordProtected: switchChecked, 30 | // [e.target.name]: e.target.value, 31 | // }); 32 | }; 33 | 34 | // ================ Handle Password Save Button ============ 35 | const handlePasswordSaveBtn = () => { 36 | // console.log(passwordDetails); 37 | if (!(passwordDetails.password === "")) { 38 | handleNotesPasswordReturn(passwordDetails); 39 | handlelockBtn(); 40 | } 41 | }; 42 | 43 | const passwordExist = () => { 44 | setPasswordDetails({ 45 | isPasswordProtected: data.isPasswordProtected, 46 | password: data.password, 47 | confirmNotesPassword: data.password, 48 | }); 49 | }; 50 | 51 | // =============== useEffect ============ 52 | useEffect(() => { 53 | notes_id ? passwordExist() : null; 54 | }, []); 55 | return ( 56 | <> 57 |
58 |
59 |

Lock Notes

60 |

Protect your notes

61 |
62 | setSwitchChecked(!switchChecked)} 67 | // onClick={() => 68 | // setPasswordDetails({ 69 | // isPasswordProtected: !passwordDetails.isPasswordProtected, 70 | // }) 71 | // } 72 | onClick={() => 73 | setPasswordDetails({ 74 | ...passwordDetails, 75 | isPasswordProtected: !passwordDetails.isPasswordProtected, 76 | }) 77 | } 78 | /> 79 | 87 |
88 |
89 | 99 |
100 |
101 | 111 | {passwordDetails.password === 112 | passwordDetails.confirmNotesPassword ? ( 113 | "" 114 | ) : ( 115 | *Password are not matching 116 | )} 117 |
118 |
119 | 125 | 131 |
132 | 133 | {/* */} 134 |
135 |
136 | 137 | ); 138 | }; 139 | 140 | export default NotesLockBtn; 141 | -------------------------------------------------------------------------------- /frontend/src/styles/login/login.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 100%; 3 | height: 100vh; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-color: #eaf7dd; 8 | } 9 | .login_container { 10 | width: 30%; 11 | /* height: 500px; */ 12 | /* box-shadow: 4px 5px 10px black; */ 13 | border-radius: 10px; 14 | padding: 25px; 15 | /* background-color: #d9eddb; */ 16 | 17 | background: rgb(217, 237, 219); 18 | background: linear-gradient( 19 | 65deg, 20 | rgba(217, 237, 219, 1) 61%, 21 | rgba(238, 255, 245, 1) 61% 22 | ); 23 | 24 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 25 | } 26 | .logo_box { 27 | /* width: 130px; */ 28 | width: 100px; 29 | } 30 | .form_title { 31 | /* text-align: center; */ 32 | font-size: 1.4rem; 33 | font-weight: bold; 34 | color: #1b561b; 35 | margin-bottom: 9px; 36 | } 37 | .login_form { 38 | width: 100%; 39 | height: 100%; 40 | display: flex; 41 | flex-direction: column; 42 | font-weight: bold; 43 | /* color: #133b13; */ 44 | color: #1b561b; 45 | font-size: 0.8rem; 46 | } 47 | .div_wrapper { 48 | width: 100%; 49 | display: flex; 50 | margin: 4px 0; 51 | } 52 | .message { 53 | /* border: 2px solid black; */ 54 | width: 100%; 55 | padding: 5px; 56 | text-align: center; 57 | /* color: #ae0404; */ 58 | border-radius: 2px; 59 | /* background-color: #ff9393; */ 60 | color: #ffffff; 61 | background-color: #c20202; 62 | } 63 | .form_input_box { 64 | width: 100%; 65 | /* margin: 0px 6px; */ 66 | 67 | /* border: 2px solid black; */ 68 | } 69 | .form_data_wrapper { 70 | width: 100%; 71 | } 72 | .input_box { 73 | width: 100%; 74 | padding: 6px; 75 | outline: none; 76 | /* border: 1px solid #133113; */ 77 | border-radius: 5px; 78 | /* background-color: #ffffff; */ 79 | /* box-shadow: 0px 1px 4px green inset; */ 80 | margin: 4px 0px; 81 | /* color: white; */ 82 | background-color: white; 83 | /* background-color: #eaf7dd; */ 84 | border: 1px solid #75ab40; 85 | } 86 | .input_box:focus { 87 | border: 2px solid #5fba5f; 88 | } 89 | .invalid_user { 90 | color: rgb(170, 28, 28); 91 | font-weight: normal; 92 | } 93 | .login_box { 94 | width: 100%; 95 | margin: 8px 6px; 96 | /* display: flex; 97 | justify-content: center; */ 98 | } 99 | .login_button { 100 | width: 100%; 101 | /* background-color: #1f5d1f; */ 102 | background-color: #217f21; 103 | color: white; 104 | border-radius: 5px; 105 | padding: 6px; 106 | margin-top: 12px; 107 | } 108 | .login_button:hover { 109 | background-color: #1f5d1f; 110 | } 111 | .login { 112 | width: 100%; 113 | margin-top: 10px; 114 | text-align: center; 115 | /* font-weight: normal; */ 116 | font-size: 0.8rem; 117 | } 118 | .login_wrapper { 119 | /* width: 96%; */ 120 | color: #133b13; 121 | } 122 | .login { 123 | color: #197819; 124 | } 125 | .forgot_password_box { 126 | width: 100%; 127 | display: flex; 128 | justify-content: space-between; 129 | } 130 | .remember_me { 131 | display: flex; 132 | color: #133b13; 133 | } 134 | .remember_me_text { 135 | margin-left: 6px; 136 | } 137 | .forgot_password_text { 138 | color: #197819; 139 | } 140 | .password_box { 141 | display: flex; 142 | justify-content: space-between; 143 | width: 100%; 144 | padding: 6px; 145 | outline: none; 146 | /* border: 1px solid #133113; */ 147 | border-radius: 5px; 148 | /* background-color: #ffffff; */ 149 | /* box-shadow: 0px 1px 4px green inset; */ 150 | margin: 4px 0px; 151 | /* color: white; */ 152 | background-color: white; 153 | /* background-color: #eaf7dd; */ 154 | border: 1px solid #75ab40; 155 | } 156 | .password_input_box { 157 | width: 90%; 158 | background-color: transparent; 159 | outline: none; 160 | border: none; 161 | } 162 | .eye_botton { 163 | /* border: 2px solid black; */ 164 | color: #919591; 165 | } 166 | /* ============== Tablets and Desktop ============= */ 167 | @media only screen and (min-width: 768px) and (max-width: 1024px) { 168 | /* tablets and desktop */ 169 | .login_container { 170 | width: 50%; 171 | /* height: 500px; */ 172 | /* box-shadow: 4px 5px 10px black; */ 173 | border-radius: 10px; 174 | padding: 25px; 175 | /* background-color: #d9eddb; */ 176 | background: rgb(217, 237, 219); 177 | background: linear-gradient( 178 | 65deg, 179 | rgba(217, 237, 219, 1) 61%, 180 | rgba(238, 255, 245, 1) 61% 181 | ); 182 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 183 | } 184 | } 185 | 186 | /* ============== Phone ================ */ 187 | @media only screen and (max-width: 767px) { 188 | /* phones */ 189 | .main { 190 | width: 100%; 191 | height: 100vh; 192 | display: flex; 193 | justify-content: center; 194 | align-items: flex-start; 195 | background-color: #eaf7dd; 196 | } 197 | .login_container { 198 | width: 80%; 199 | /* height: 500px; */ 200 | /* box-shadow: 4px 5px 10px black; */ 201 | border-radius: 10px; 202 | padding: 25px; 203 | /* background-color: #d9eddb; */ 204 | background: rgb(217, 237, 219); 205 | background: linear-gradient( 206 | 65deg, 207 | rgba(217, 237, 219, 1) 61%, 208 | rgba(238, 255, 245, 1) 61% 209 | ); 210 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 211 | } 212 | .form_title { 213 | /* text-align: center; */ 214 | font-size: 1.1rem; 215 | font-weight: bold; 216 | color: #1b561b; 217 | margin-bottom: 9px; 218 | } 219 | .login { 220 | width: 100%; 221 | margin-top: 10px; 222 | text-align: center; 223 | /* font-weight: normal; */ 224 | font-size: 0.7rem; 225 | } 226 | .login_container { 227 | width: 80%; 228 | height: max-content; 229 | margin-top: 70px; 230 | /* box-shadow: 4px 5px 10px black; */ 231 | border-radius: 10px; 232 | padding: 25px; 233 | /* background-color: #d9eddb; */ 234 | background: rgb(217, 237, 219); 235 | background: linear-gradient( 236 | 65deg, 237 | rgba(217, 237, 219, 1) 61%, 238 | rgba(238, 255, 245, 1) 61% 239 | ); 240 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /frontend/src/components/ChangeProfileImage/ChangeProfileImage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import picturesData from "../../database/picturesData.json"; 3 | import { apiRoutes } from "@/utils/apiRoutes"; 4 | import axios from "axios"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { getData } from "@/features/user/userSlice"; 7 | const ChangeProfileImage = ({ handleImageChangeBtn, profilePic }) => { 8 | const [changeImageBtn, setChangeImageBtn] = useState(false); 9 | 10 | const handleChangeImageBtn = () => { 11 | setChangeImageBtn(!changeImageBtn); 12 | }; 13 | return ( 14 | <> 15 |
18 |
21 |
22 | 28 |
29 |
30 | 36 | 37 | 43 |
44 |
45 |
46 | {/* ================== Change Image Component ======================== */} 47 | {changeImageBtn ? ( 48 | 49 | ) : null} 50 | 51 | ); 52 | }; 53 | 54 | const ChangeImage = ({ handleChangeImageBtn }) => { 55 | const [pictures, setPictures] = useState([]); 56 | const [selectPhoto, setSelectPhoto] = useState(""); 57 | const userId = useSelector((state) => state.user.value); 58 | const dispatch = useDispatch(); 59 | const handleSelectPhoto = (select) => { 60 | // console.log(select); 61 | setSelectPhoto(select); 62 | }; 63 | const handleSaveBtn = () => { 64 | // console.log(selectPhoto); 65 | // console.log(userId._id); 66 | 67 | handleApiCalling(); 68 | handleChangeImageBtn(); 69 | }; 70 | const handleApiCalling = async () => { 71 | try { 72 | const token = localStorage.getItem("notebookToken"); 73 | const response = await axios.put( 74 | `${apiRoutes.updateUserProfileURI}/${userId._id}`, 75 | { profilePic: selectPhoto }, 76 | { headers: { Authorization: `Bearer ${token}` } } 77 | ); 78 | dispatch(getData(response.data.data)); 79 | } catch (error) { 80 | console.log("err", error); 81 | } 82 | }; 83 | useEffect(() => { 84 | setPictures(picturesData); 85 | }, []); 86 | return ( 87 | <> 88 |
91 |
94 |
95 | {pictures.map((item, index) => ( 96 |
handleSelectPhoto(item.path)} 104 | > 105 | 106 |
107 | )) ||
Loading ...
} 108 |
109 |
110 | 116 | 122 |
123 |
124 |
125 | 126 | ); 127 | }; 128 | 129 | export default ChangeProfileImage; 130 | 131 | // {pictures.map((item, index) => ( 132 | //
133 | // 134 | //
135 | // ))} 136 | 137 | // View Change Image Remove cancel 138 | 139 | { 140 | /*
143 |
144 | 150 | 151 | 156 | 157 | 162 | 163 | 169 |
170 |
; */ 171 | } 172 | -------------------------------------------------------------------------------- /frontend/src/components/ui/dropdown-menu.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef(({ className, inset, children, ...props }, ref) => ( 22 | 30 | {children} 31 | 32 | 33 | )) 34 | DropdownMenuSubTrigger.displayName = 35 | DropdownMenuPrimitive.SubTrigger.displayName 36 | 37 | const DropdownMenuSubContent = React.forwardRef(({ className, ...props }, ref) => ( 38 | 45 | )) 46 | DropdownMenuSubContent.displayName = 47 | DropdownMenuPrimitive.SubContent.displayName 48 | 49 | const DropdownMenuContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => ( 50 | 51 | 59 | 60 | )) 61 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 62 | 63 | const DropdownMenuItem = React.forwardRef(({ className, inset, ...props }, ref) => ( 64 | 72 | )) 73 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 74 | 75 | const DropdownMenuCheckboxItem = React.forwardRef(({ className, children, checked, ...props }, ref) => ( 76 | 84 | 85 | 86 | 87 | 88 | 89 | {children} 90 | 91 | )) 92 | DropdownMenuCheckboxItem.displayName = 93 | DropdownMenuPrimitive.CheckboxItem.displayName 94 | 95 | const DropdownMenuRadioItem = React.forwardRef(({ className, children, ...props }, ref) => ( 96 | 103 | 104 | 105 | 106 | 107 | 108 | {children} 109 | 110 | )) 111 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 112 | 113 | const DropdownMenuLabel = React.forwardRef(({ className, inset, ...props }, ref) => ( 114 | 118 | )) 119 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 120 | 121 | const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => ( 122 | 126 | )) 127 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 128 | 129 | const DropdownMenuShortcut = ({ 130 | className, 131 | ...props 132 | }) => { 133 | return ( 134 | () 137 | ); 138 | } 139 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 140 | 141 | export { 142 | DropdownMenu, 143 | DropdownMenuTrigger, 144 | DropdownMenuContent, 145 | DropdownMenuItem, 146 | DropdownMenuCheckboxItem, 147 | DropdownMenuRadioItem, 148 | DropdownMenuLabel, 149 | DropdownMenuSeparator, 150 | DropdownMenuShortcut, 151 | DropdownMenuGroup, 152 | DropdownMenuPortal, 153 | DropdownMenuSub, 154 | DropdownMenuSubContent, 155 | DropdownMenuSubTrigger, 156 | DropdownMenuRadioGroup, 157 | } 158 | -------------------------------------------------------------------------------- /frontend/src/pages/About/About.jsx: -------------------------------------------------------------------------------- 1 | import SidebarMenu from "@/common/SidebarMenu/SidebarMenu"; 2 | import React from "react"; 3 | 4 | const features = [ 5 | { 6 | title: "Create and Edit Notes", 7 | text: " Effortlessly write and format your notes with our intuitive editor.", 8 | }, 9 | { 10 | title: "Protect Your Privacy", 11 | text: "Keep your notes secure with password protection and encryption.", 12 | }, 13 | { 14 | title: "Export and Share", 15 | text: "Export your notes to PDF or Txt, or copy them to your clipboard with ease.", 16 | }, 17 | { 18 | title: "Manage To-Do Lists", 19 | text: "Stay on top of your tasks with our versatile to-do list feature", 20 | }, 21 | { 22 | title: "Customize Your Experience", 23 | text: " Choose from a variety of themes and styles to personalize your notebook.", 24 | }, 25 | { 26 | title: "Search and Sort", 27 | text: "Quickly find and organize your notes with our powerful search and sort functions.", 28 | }, 29 | { 30 | title: "User Accounts", 31 | text: " Create an account to access your notes from any device, update your profile, and ensure your data is safe", 32 | }, 33 | ]; 34 | const choosePoint = [ 35 | { 36 | title: "User-Friendly", 37 | text: "Our interface is designed to be simple and intuitive, so you can focus on what matters.", 38 | }, 39 | { 40 | title: "Secure", 41 | text: "We prioritize your privacy and data security, with robust protection measures in place.", 42 | }, 43 | { 44 | title: "Flexible", 45 | text: "From note-taking to task management, Notebook adapts to your workflow and preferences.", 46 | }, 47 | { 48 | title: "Beautiful Content", 49 | text: "With rich text formatting and media support, your notes will always look great.", 50 | }, 51 | ]; 52 | const About = () => { 53 | return ( 54 | <> 55 |
56 |
57 | 58 |
59 |
60 |

61 | About Us 62 |

63 |
64 |
65 |
66 |

67 | Welcome to Notebook, your ultimate tool for capturing 68 | thoughts, organizing tasks, and creating beautiful content. 69 | Whether you're a student, professional, or just someone who 70 | loves to jot down ideas, Notebook is designed to meet your 71 | needs with simplicity and style. 72 |

73 |

74 | Our Mission 75 |

76 |
77 |

78 | At Notebook, our mission is to provide a seamless and secure 79 | environment for your notes and tasks. We believe that 80 | organization and creativity go hand-in-hand, and our goal is 81 | to empower you with the tools to achieve both. 82 |

83 |
84 | 85 |
86 | about_us 96 |
97 |
98 |
99 |
100 | https://cdn.pixabay.com/photo/2024/04/28/10/15/ai-generated-8725235_960_720.jpg 105 |
106 |
107 |

108 | Features 109 |

110 |
111 |
    112 | {features.map((item, index) => ( 113 |
  • 117 | 118 | {/* 🌟 */} 119 | {/* ⭐ */}✨{item.title}:{" "} 120 | 121 |

    {item.text}

    122 |
  • 123 | ))} 124 |
125 |
126 |
127 |
128 |

129 | Why Choose Notebook? 130 |

131 |
132 |
    133 | {choosePoint.map((item, index) => ( 134 |
  • 138 | 139 | {item.title} : 140 | 141 |

    {item.text}

    142 |
  • 143 | ))} 144 |
145 |
146 |
147 |

148 | Thank you for choosing Notebook. We look forward to helping you 149 | stay organized and inspired. 150 |

151 |
152 |
153 |
154 |
155 | 156 | ); 157 | }; 158 | 159 | export default About; 160 | -------------------------------------------------------------------------------- /frontend/src/pages/UsreProfileSettings/UserProfileSettings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link, NavLink, useNavigate } from "react-router-dom"; 3 | import { IoMdArrowBack } from "react-icons/io"; 4 | import { RiDeleteBinFill } from "react-icons/ri"; 5 | import { RiDeleteBin7Fill } from "react-icons/ri"; 6 | import { IoMdEyeOff } from "react-icons/io"; 7 | import { IoMdEye } from "react-icons/io"; 8 | import { GiPadlock } from "react-icons/gi"; 9 | import { AiFillLock } from "react-icons/ai"; 10 | import { apiRoutes } from "@/utils/apiRoutes"; 11 | import { jwtDecode } from "jwt-decode"; 12 | import axios from "axios"; 13 | import Loading from "@/components/Loading/Loading"; 14 | const UserProfileSettings = () => { 15 | const [backBtn, setBackBtn] = useState(false); 16 | const handleBackBtn = () => { 17 | setBackBtn(!backBtn); 18 | }; 19 | 20 | return ( 21 | <> 22 |
25 |
28 | {/* ======================== Back Button =================== */} 29 |
30 | 34 | 35 | Back 36 | 37 |
38 |

39 | Settings 40 |

41 | 42 |
43 | 49 | Reset Password 50 | 51 |
52 | 53 | {/* =========================== Delete Button =========================== */} 54 |
55 |
56 |

57 | Delete Your Notebook Account 58 |

59 | {/*
*/} 60 | 66 |
67 |
68 |
69 | {/* ======================= Delete Btn Component ============= */} 70 | {backBtn ? : ""} 71 | 72 | ); 73 | }; 74 | 75 | // ==================== Create Delete Btn Component =================== 76 | const DeleteBtnComponent = ({ handleBackBtn }) => { 77 | const [passwordShowBtn, setPasswordShowBtn] = useState(false); 78 | const [password, setPassword] = useState(""); 79 | const [passwordValidationMessage, setPasswordValidationMessage] = 80 | useState(""); 81 | const [loading, setLoading] = useState(false); 82 | const navigate = useNavigate(); 83 | const handlePasswordInput = (e) => { 84 | setPassword(e.target.value); 85 | }; 86 | 87 | const handleApiCalling = async () => { 88 | try { 89 | // console.log("pass", password); 90 | const token = localStorage.getItem("notebookToken"); 91 | const user = token ? jwtDecode(token) : null; 92 | setLoading(true); 93 | const response = await axios.delete( 94 | `${apiRoutes.deleteUserProfileURI}/${user._id}`, 95 | 96 | { 97 | headers: { Authorization: `Bearer ${token}` }, 98 | data: { 99 | password: password, 100 | }, 101 | } 102 | ); 103 | setLoading(false); 104 | // console.log("res", response.data.statusInfo); 105 | if (response.data.statusInfo === "success") { 106 | setPasswordValidationMessage(""); 107 | localStorage.removeItem("notebookToken"); 108 | window.location.reload(); 109 | navigate("/user/login"); 110 | } 111 | } catch (error) { 112 | setLoading(false); 113 | // console.log("err", error.response.data.message); 114 | setPasswordValidationMessage(error.response.data.message); 115 | } 116 | }; 117 | 118 | const handleDeleteBtn = () => { 119 | handleApiCalling(); 120 | // console.log(password); 121 | }; 122 | return ( 123 | <> 124 | {loading ? : ""} 125 |
126 |
129 |

130 | Delete Your Account 131 |

132 |
e.preventDefault()} 136 | > 137 | 145 |
146 | 151 | 157 |
158 | 159 | 160 | {passwordValidationMessage === "please enter password correctly" 161 | ? "*Incorrect Password" 162 | : passwordValidationMessage === "wrong password" 163 | ? "*Incorrect Password" 164 | : ""} 165 | 166 | 172 | 178 |
179 |
180 |
181 | 182 | ); 183 | }; 184 | export default UserProfileSettings; 185 | -------------------------------------------------------------------------------- /backend/src/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import { User } from "../models/user.model.js"; 2 | import ApiError from "../utils/ApiError.js"; 3 | import asyncErrorHandler from "../utils/asyncErrorHandler.js"; 4 | import { generateToken } from "../middlewares/auth.middleware.js"; 5 | import { encryPassword } from "../utils/hashPassword.js"; 6 | import ApiResponse from "../utils/apiResponse.js"; 7 | import { Notes } from "../models/notes.model.js"; 8 | 9 | //=================== Sign Up =================== 10 | const signup = asyncErrorHandler(async (req, res) => { 11 | const getData = req.body; 12 | const checkUserName = await User.findOne({ username: getData.username }); 13 | const checkEmail = await User.findOne({ email: getData.email }); 14 | 15 | // ------------------ check duplicate username and email ------------------ 16 | if (checkUserName) { 17 | // ------------------ throwing error in ApiError.js file ------------------ 18 | throw new ApiError(400, "error", "username already exists"); 19 | } else if (checkEmail) { 20 | throw new ApiError(400, "error", "email already exists"); 21 | } else { 22 | const newUser = new User(getData); 23 | const data = await newUser.save(); 24 | 25 | const payload = { 26 | _id: data._id, 27 | name: data.name, 28 | username: data.username, 29 | email: data.email, 30 | }; 31 | // -------------- Generate Token ---------------- 32 | const token = generateToken(payload); 33 | 34 | // ------------- Password Remove --------- 35 | const userData = data.toObject(); 36 | delete userData.password; 37 | // res.status(200).json({ 38 | // status: 200, 39 | // statusInfo: "success", 40 | // data: data, 41 | // token: token, 42 | // }); 43 | res.status(200).json(new ApiResponse(200, userData, token, "success")); 44 | } 45 | }); 46 | 47 | // =================== Log in =================== 48 | const login = asyncErrorHandler(async (req, res) => { 49 | const { email, password } = req.body; 50 | 51 | // ---------- check email and password exists or not --------- 52 | const user = await User.findOne({ email: email }); 53 | 54 | if (!user || !(await user.comparePassword(password))) { 55 | // ------------------ throwing error in errorHandler.js file ------------------ 56 | // throw { 57 | // status: 401, 58 | // statusInfo: "error", 59 | // response: "email or password doesn't exists", 60 | // }; 61 | throw new ApiError(401, "error", "email or password doesn't exists"); 62 | } 63 | // ------------ Generate Token ---------- 64 | const payload = { 65 | _id: user._id, 66 | name: user.name, 67 | username: user.username, 68 | email: user.email, 69 | }; 70 | const token = generateToken(payload); 71 | // ------------ Password Remove ---------- 72 | const userData = user.toObject(); 73 | delete userData.password; 74 | // ------------------ response send ------------- 75 | res.status(200).json(new ApiResponse(200, userData, token, "success")); 76 | // res 77 | // .status(200) 78 | // .json({ status: 200, statusInfo: "success", response: user, token: token }); 79 | }); 80 | 81 | // =================== Reset Password =================== 82 | const resetPassword = asyncErrorHandler(async (req, res) => { 83 | const { email, password } = req.body; 84 | const hashedPassword = await encryPassword(password); 85 | 86 | const user = await User.findOneAndUpdate( 87 | { 88 | email: email, 89 | }, 90 | { 91 | password: hashedPassword, 92 | }, 93 | { 94 | new: true, 95 | } 96 | ); 97 | if (!user) { 98 | throw new ApiError(404, "fail", "user not found"); 99 | } else { 100 | res 101 | .status(200) 102 | .json( 103 | new ApiResponse( 104 | 200, 105 | null, 106 | null, 107 | "success", 108 | "password changed successfully" 109 | ) 110 | ); 111 | // res.status(200).json(new ApiResponse(200, user)); 112 | // res 113 | // .status(200) 114 | // .json({ status: 200, statusInfo: "success", response: user }); 115 | } 116 | }); 117 | 118 | // =================== Update User Profile =================== 119 | const updateUserProfile = asyncErrorHandler(async (req, res) => { 120 | const { id } = req.params; 121 | 122 | // ---------------- Check username exist or not ----------------- 123 | const existUsername = 124 | (await User.findOne({ username: req.body.username })) || {}; 125 | if (existUsername.username === (req.body.username || false)) { 126 | throw new ApiError(401, "fail", "username already exist"); 127 | } 128 | // ---------------- Check email exist or not ------------ 129 | const existEmail = (await User.findOne({ email: req.body.email })) || {}; 130 | if (existEmail.email === (req.body.email || false)) { 131 | throw new ApiError(401, "fail", "email already exist"); 132 | } 133 | 134 | const updateUser = await User.findByIdAndUpdate(id, req.body, { 135 | returnNewDocument: true, 136 | new: true, 137 | }); 138 | 139 | if (!updateUser) { 140 | throw new ApiError(404, "fail", "user not found"); 141 | } 142 | // ------------- Password Remove ---------- 143 | const userData = updateUser.toObject(); 144 | delete userData.password; 145 | res 146 | .status(200) 147 | .json( 148 | new ApiResponse( 149 | 200, 150 | userData, 151 | null, 152 | "success", 153 | "user details updated successfully" 154 | ) 155 | ); 156 | // res.status(200).json(new ApiResponse(200, updateUser)); 157 | // res 158 | // .status(200) 159 | // .json({ status: 200, statusInfo: "success", response: updateUser }); 160 | }); 161 | 162 | // =================== Delete User Profile =================== 163 | const deleteUserProfile = asyncErrorHandler(async (req, res) => { 164 | const { id } = req.params; 165 | const { password } = req.body; 166 | const user = await User.findOne({ _id: id }); 167 | 168 | // ------------- user exist or not -------- 169 | if (!user) { 170 | throw new ApiError(404, "fail", "user not found"); 171 | } 172 | 173 | // ------------ Enter password before deleting account ---------- 174 | // -------------- compare password ----------------- 175 | if (!password) { 176 | throw new ApiError(401, "fail", "please enter password correctly"); 177 | } 178 | const userPassword = await user.comparePassword(password); 179 | 180 | // ------------- check password correct or wrong -------------- 181 | if (!userPassword) { 182 | throw new ApiError(401, "fail", "wrong password"); 183 | } 184 | const deleteUser = await User.findByIdAndDelete({ _id: id }); 185 | let DeleteId = deleteUser._id.toString(); 186 | 187 | if (!deleteUser) { 188 | throw new ApiError(404, "fail", "user not found"); 189 | } 190 | 191 | // ----------------- Delete All User Notes ---------------------- 192 | const deleteAllData = await Notes.deleteMany({ 193 | author: DeleteId, 194 | }); 195 | 196 | res 197 | .status(200) 198 | .json( 199 | new ApiResponse( 200 | 200, 201 | null, 202 | null, 203 | "success", 204 | "account deleted successfully" 205 | ) 206 | ); 207 | // res.status(200).json({ 208 | // status: 200, 209 | // statusInfo: "success", 210 | // response: "account deleted successfully", 211 | // }); 212 | }); 213 | 214 | // =================== Get User Profile =================== 215 | const getUserProfile = asyncErrorHandler(async (req, res) => { 216 | const { id } = req.params; 217 | const getUser = await User.findById({ _id: id }); 218 | 219 | if (!getUser) { 220 | throw new ApiError(404, "fail", "user not found"); 221 | } 222 | // ------------- Password Remove ---------- 223 | const userData = getUser.toObject(); 224 | delete userData.password; 225 | 226 | res.status(200).json(new ApiResponse(200, userData)); 227 | // res.status(200).json({ msg: "OK" }); 228 | }); 229 | 230 | // =================== Export =================== 231 | export { 232 | signup, 233 | login, 234 | resetPassword, 235 | updateUserProfile, 236 | deleteUserProfile, 237 | getUserProfile, 238 | }; 239 | -------------------------------------------------------------------------------- /frontend/src/pages/Login/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import logo from "/notebook.png"; 3 | import { HiEyeOff } from "react-icons/hi"; 4 | import { HiEye } from "react-icons/hi"; 5 | import { Link, useNavigate } from "react-router-dom"; 6 | import styles from "../../styles/login/login.module.css"; 7 | import { apiRoutes } from "@/utils/apiRoutes"; 8 | import axios from "axios"; 9 | import { useDispatch } from "react-redux"; 10 | import { ToastContainer, toast } from "react-toastify"; 11 | import { getData } from "@/features/user/userSlice"; 12 | import Loading from "@/components/Loading/Loading"; 13 | const Login = () => { 14 | // -------------------- State Start ------------------------ 15 | const [data, setData] = useState({ 16 | email: "", 17 | password: "", 18 | }); 19 | const [validEmail, setValidEmail] = useState(false); 20 | const [validPassword, setValidPassword] = useState(false); 21 | const [showPassword, setShowPassword] = useState(false); 22 | const [message, setMessage] = useState(false); 23 | const [loading, setLoading] = useState(false); 24 | // ------------------ State End ------------------------ 25 | const dispatch = useDispatch(); 26 | const navigate = useNavigate(); 27 | const handleInputBox = (e) => { 28 | setData({ ...data, [e.target.name]: e.target.value }); 29 | }; 30 | const handleLogIn = () => { 31 | // =================== Email validation =================== 32 | const emailPattern = 33 | /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; 34 | if (emailPattern.test(data.email)) { 35 | setValidEmail(false); 36 | } else { 37 | setValidEmail(true); 38 | } 39 | 40 | // ================== Password validation =============== 41 | if (!(data.password === "")) { 42 | setValidPassword(false); 43 | } else { 44 | setValidPassword(true); 45 | } 46 | if ( 47 | validEmail === false && 48 | !(data.email === "") && 49 | validPassword === false && 50 | !(data.password === "") 51 | ) { 52 | handleApiCalling(data); 53 | } 54 | // console.log(data); 55 | }; 56 | const handleApiCalling = async (data) => { 57 | try { 58 | setLoading(true); 59 | const response = await axios.post(apiRoutes.loginURI, data, { 60 | headers: { 61 | "Content-Type": "application/json", 62 | }, 63 | }); 64 | setLoading(false); 65 | localStorage.setItem("notebookToken", response.data.token); 66 | setMessage(false); 67 | toast.success("Login Successfully", { 68 | position: "top-center", 69 | }); 70 | // navigate("/"); 71 | navigate("/user/notes"); 72 | window.location.reload(); 73 | } catch (error) { 74 | setLoading(false); 75 | // console.log("err", error.response.data.message); 76 | if ("email or password doesn't exists" === error.response.data.message) { 77 | setMessage(true); 78 | } 79 | } 80 | }; 81 | return ( 82 | <> 83 | {loading ? : ""} 84 |
85 |
86 |
87 | logo 88 |
89 |

Sign in to your account

90 |
e.preventDefault()} 94 | > 95 | {message ? ( 96 |
97 | {/* ===================== Email ======================= */} 98 | User doesn't exist 99 |
100 | ) : null} 101 |
102 | {/* ===================== Email ======================= */} 103 |
104 | 114 | {validEmail ? ( 115 | 116 | Invalid Email Addresss 117 | 118 | ) : null} 119 |
120 |
121 | 122 |
123 | {/* ===================== Password ======================= */} 124 |
125 | 152 | {validPassword ? ( 153 | 154 | *Please Enter Your Password 155 | 156 | ) : null} 157 |
158 |
159 |
160 | 166 | 171 | Forgot password? 172 | 173 |
174 | {/*
*/} 175 |
176 | 182 |
183 | {/*
*/} 184 | {/*
*/} 185 |
186 | 187 | Don’t have an account yet?{" "} 188 | 189 | Sign up here 190 | 191 | 192 |
193 |
194 |
195 |
196 | 197 | 198 | ); 199 | }; 200 | 201 | export default Login; 202 | -------------------------------------------------------------------------------- /frontend/src/common/Navbar/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import styles from "../../styles/navbar/navbar.module.css"; 3 | import logo from "/notebook.png"; 4 | import { RxHamburgerMenu } from "react-icons/rx"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { menuToggle } from "@/features/menu/menuSlice"; 7 | import { IoMdClose } from "react-icons/io"; 8 | import { Link, NavLink, Router } from "react-router-dom"; 9 | import { MdEditDocument } from "react-icons/md"; 10 | import { FaList } from "react-icons/fa"; 11 | import { BsInfoSquareFill } from "react-icons/bs"; 12 | import { IoMdMail } from "react-icons/io"; 13 | import { IoSettingsSharp } from "react-icons/io5"; 14 | import { FaLinkedin } from "react-icons/fa"; 15 | import { FaGithub } from "react-icons/fa6"; 16 | import { BsTwitterX } from "react-icons/bs"; 17 | import { FaGlobe } from "react-icons/fa"; 18 | import { GetUserData } from "@/utils/userApiCall"; 19 | let token = localStorage.getItem("notebookToken"); 20 | const Navbar = () => { 21 | const [auth, setAuth] = useState(null); 22 | 23 | const [userData, setUserData] = useState([]); 24 | const menuBtn = useSelector((state) => state.menu.value); 25 | const dispatch = useDispatch(); 26 | 27 | const userDataCalling = async () => { 28 | let data = token ? await GetUserData() : []; 29 | 30 | setUserData(data); 31 | }; 32 | 33 | useEffect(() => { 34 | userDataCalling(); 35 | if (token) { 36 | setAuth(token); 37 | } 38 | }, []); 39 | 40 | return ( 41 | <> 42 |
43 |
44 |
45 | logo 46 |
47 | 53 |
54 | {/*
*/} 55 | {/* ================================================================= */} 56 |
61 |
62 | {auth ? ( 63 | <> 64 |
65 |
66 | 67 | {userData.profilePic 72 | 73 |
74 |
75 | dispatch(menuToggle())} 79 | > 80 | {userData.name} 81 | 82 | 83 | dispatch(menuToggle())} 87 | > 88 | Profile 89 | 90 |
91 |
92 | 93 |
94 | dispatch(menuToggle())} 98 | > 99 | 100 | Notes 101 | 102 |
103 |
104 | dispatch(menuToggle())} 108 | > 109 | 110 | Todo List 111 | 112 |
113 |
114 | dispatch(menuToggle())} 118 | > 119 | 120 | About 121 | 122 |
123 |
124 | dispatch(menuToggle())} 128 | > 129 | 130 | Contact Us 131 | 132 |
133 |
134 | dispatch(menuToggle())} 138 | > 139 | 140 | Settings 141 | 142 |
143 | 144 | ) : ( 145 | <> 146 |
147 |
148 | dispatch(menuToggle())} 152 | > 153 | {/* */} 154 | Sign Up 155 | 156 |
157 |
158 | dispatch(menuToggle())} 162 | > 163 | {/* */} 164 | Log in 165 | 166 |
167 |
168 | 169 | )} 170 |
171 |
172 |
Follow Me
173 | 190 |
191 |
192 |
193 |
194 | 195 | ); 196 | }; 197 | 198 | export default Navbar; 199 | -------------------------------------------------------------------------------- /frontend/src/pages/TodoList/TodoList.jsx: -------------------------------------------------------------------------------- 1 | import SidebarMenu from "@/common/SidebarMenu/SidebarMenu"; 2 | import React, { useEffect, useState } from "react"; 3 | import Modal from "react-modal"; 4 | import { FaEdit } from "react-icons/fa"; 5 | import { MdDelete } from "react-icons/md"; 6 | Modal.setAppElement("#root"); 7 | const customStyles = { 8 | content: { 9 | top: "50%", 10 | left: "50%", 11 | right: "auto", 12 | bottom: "auto", 13 | marginRight: "-50%", 14 | transform: "translate(-50%, -50%)", 15 | }, 16 | }; 17 | 18 | // ================ Todo List Component Start ================= 19 | const TodoList = () => { 20 | const [modalIsOpen, setIsOpen] = useState(false); 21 | const [updateModal, setUpdateModal] = useState(false); 22 | const [listData, setListData] = useState([]); 23 | const [listItem, setListItem] = useState(""); 24 | const [indexNo, setIndexNo] = useState(null); 25 | const [checkBox, setCheckBox] = useState([]); 26 | function openModal() { 27 | setIsOpen(true); 28 | } 29 | 30 | function afterOpenModal() {} 31 | 32 | function closeModal() { 33 | setIsOpen(false); 34 | } 35 | 36 | function closeUpdateModal() { 37 | setUpdateModal(false); 38 | } 39 | function openUpdateModal() { 40 | setUpdateModal(true); 41 | } 42 | 43 | const getData = (listContent) => { 44 | let data = listData; 45 | let temp = data.push(listContent); 46 | 47 | setListData(data); 48 | }; 49 | 50 | const updateData = (data, index) => { 51 | openUpdateModal(); 52 | setListItem(data); 53 | 54 | setIndexNo(index); 55 | }; 56 | const updateListValue = (data) => { 57 | listData[indexNo] = data; 58 | }; 59 | const handleDeleteBtn = (listIndex) => { 60 | setListData(listData.filter((item, index) => !(listIndex === index))); 61 | }; 62 | 63 | const handleCheckBox = (index) => {}; 64 | return ( 65 | <> 66 |
67 |
68 | 69 |
70 |
71 |

72 | Todo List 73 |

74 | 75 | Note :- This data will destroy if you close this tab or refresh 76 | this tab 77 | 78 |
79 | 85 | 91 |
92 |
93 | {!(listData.length === 0) ? ( 94 | listData.map((item, index) => ( 95 |
99 |
100 | handleCheckBox(index)} 106 | /> 107 | 110 |
111 | 112 |
113 | 120 | 127 |
128 |
129 | )) 130 | ) : ( 131 |
132 | Empty List 133 |
134 | )} 135 |
136 |
137 |
138 |
139 |
140 | {/* =============== Add Task Modal ==================== */} 141 | 142 | 147 | {/* =============== Update Task Modal ==================== */} 148 | {updateModal ? ( 149 | 155 | ) : ( 156 | "" 157 | )} 158 | 159 | ); 160 | }; 161 | 162 | const CreateAndUpdateList = ({ modalIsOpen, closeModal, getData }) => { 163 | const [listContent, setListContent] = useState(); 164 | const handleAddBtn = () => { 165 | closeModal(); 166 | getData(listContent); 167 | }; 168 | return ( 169 | 177 |
178 |

Create List

179 | setListContent(e.target.value)} 183 | className="py-2 px-3 outline-none border border-green-700 rounded" 184 | /> 185 |
186 | 192 | 198 |
199 |
200 |
201 | ); 202 | }; 203 | 204 | const UpdateDataList = ({ 205 | updateModal, 206 | closeUpdateModal, 207 | updateListValue, 208 | listItem, 209 | }) => { 210 | const [listContent, setListContent] = useState(""); 211 | const handleInputChange = (e) => { 212 | setListContent(e.target.value); 213 | }; 214 | 215 | const handleAddBtn = () => { 216 | updateListValue(listContent); 217 | 218 | closeUpdateModal(); 219 | }; 220 | 221 | useEffect(() => { 222 | setListContent(listItem); 223 | }, []); 224 | return ( 225 |
230 |
231 |

Update List

232 | 239 |
240 | 246 | 252 |
253 |
254 |
255 | ); 256 | }; 257 | export default TodoList; 258 | -------------------------------------------------------------------------------- /frontend/src/pages/ContactUs/ContactUs.jsx: -------------------------------------------------------------------------------- 1 | import SidebarMenu from "@/common/SidebarMenu/SidebarMenu"; 2 | import React, { useEffect, useState } from "react"; 3 | import { SiGmail } from "react-icons/si"; 4 | import { FaGlobe } from "react-icons/fa"; 5 | import { FaUserAlt } from "react-icons/fa"; 6 | import { IoIosMail } from "react-icons/io"; 7 | import { RiMessage2Fill } from "react-icons/ri"; 8 | import { IoSend } from "react-icons/io5"; 9 | import { FaLinkedin } from "react-icons/fa6"; 10 | import { FaGithub } from "react-icons/fa"; 11 | import { BsTwitterX } from "react-icons/bs"; 12 | import { NavLink } from "react-router-dom"; 13 | import { GetUserData } from "@/utils/userApiCall"; 14 | import Loading from "@/components/Loading/Loading"; 15 | 16 | const ContactUs = () => { 17 | const [data, setData] = useState({}); // this is data 18 | const [date, setDate] = useState(null); // this is date 19 | const [loading, setLoading] = useState(false); 20 | const getApiData = async () => { 21 | setLoading(true); 22 | let tempData = await GetUserData(); 23 | setLoading(false); 24 | setData(tempData); 25 | }; 26 | const getCurrentYear = () => { 27 | let year = new Date(); 28 | 29 | setDate(year.getFullYear()); 30 | }; 31 | 32 | useEffect(() => { 33 | getApiData(); 34 | getCurrentYear(); 35 | }, []); 36 | return ( 37 | <> 38 | {loading ? : ""} 39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 |

47 | Contact Us 48 |

49 |
50 | 67 |
72 |
73 | {" "} 79 | 88 |
89 |
90 | {" "} 96 | 105 |
106 |
107 | {" "} 113 | 119 |
120 | 121 | 127 |
128 |
129 |
130 |
131 |
132 | contact_us 143 |
144 |
145 |
146 | 147 |
148 |
149 | 173 |
174 | Notes 175 | Todo List 176 | Profile 177 | About 178 | {/* Contact Us */} 179 |
180 |
181 |
182 | Copyright © {date}, Notebook. All Rights Reserved. 183 |
184 |
185 |
186 |
187 |
188 | 189 | ); 190 | }; 191 | 192 | export default ContactUs; 193 | -------------------------------------------------------------------------------- /frontend/src/common/SidebarMenu/SidebarMenu.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link, NavLink } from "react-router-dom"; 3 | import { MdEditDocument } from "react-icons/md"; 4 | import { FaList } from "react-icons/fa"; 5 | import { BsInfoSquareFill } from "react-icons/bs"; 6 | import { IoMdMail } from "react-icons/io"; 7 | import { IoSettingsSharp } from "react-icons/io5"; 8 | import { FaLinkedin } from "react-icons/fa"; 9 | import { FaGithub } from "react-icons/fa6"; 10 | import { BsTwitterX } from "react-icons/bs"; 11 | import { FaGlobe } from "react-icons/fa"; 12 | import { GetUserData } from "@/utils/userApiCall"; 13 | import Loading from "@/components/Loading/Loading"; 14 | // import { useSelector } from "react-redux"; 15 | const SidebarMenu = () => { 16 | const [userData, setUserData] = useState([]); 17 | const [loading, setLoading] = useState(false); 18 | // const menuBtn = useSelector((state) => state.menu.value); 19 | 20 | const userDataCalling = async () => { 21 | setLoading(true); 22 | let data = await GetUserData(); 23 | setLoading(false); 24 | setUserData(data); 25 | }; 26 | useEffect(() => { 27 | userDataCalling(); 28 | }, []); 29 | return ( 30 | <> 31 | {loading ? : ""} 32 |
35 |
36 |
37 |
38 | 39 | {userData.profilePic} 45 | 46 |
47 |
48 | 52 | {/* Jabed Ali Mollah */} 53 | {userData.name} 54 | 55 | 56 | 60 | Profile 61 | 62 |
63 |
64 | 65 |
66 | {/* 70 | 71 | Notes 72 | */} 73 | 77 | 78 | Notes 79 | 80 |
81 |
82 | 86 | 87 | Todo List 88 | 89 |
90 |
91 | 95 | 96 | About 97 | 98 |
99 |
100 | 104 | 105 | Contact Us 106 | 107 |
108 |
109 | 113 | 114 | Settings 115 | 116 |
117 |
118 |
119 |
Follow Me
120 | 137 |
138 |
139 |
140 | 141 | ); 142 | }; 143 | 144 | export default SidebarMenu; 145 | 146 | { 147 | /* ================================================================= */ 148 | } 149 | { 150 | /*
155 |
156 |
157 |
158 | 159 | {userData.profilePic} 165 | 166 |
167 |
168 | 169 | 170 | {userData.name} 171 | 172 | 173 | 174 | Profile 175 | 176 |
177 |
178 | 179 |
180 | 181 | 185 | 186 | Notes 187 | 188 |
189 |
190 | 194 | 195 | Todo List 196 | 197 |
198 |
199 | 203 | 204 | About 205 | 206 |
207 |
208 | 212 | 213 | Contact Us 214 | 215 |
216 |
217 | 221 | 222 | Settings 223 | 224 |
225 |
226 |
227 |
Follow Me
228 | 242 |
243 |
244 |
; */ 245 | } 246 | -------------------------------------------------------------------------------- /frontend/src/pages/ForgotPassword/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { HiEye } from "react-icons/hi"; 3 | import { HiEyeOff } from "react-icons/hi"; 4 | import { Link, useNavigate } from "react-router-dom"; 5 | import logo from "/notebook.png"; 6 | import styles from "../../styles/forgotPassword/forgotPassword.module.css"; 7 | import { apiRoutes } from "@/utils/apiRoutes"; 8 | import axios from "axios"; 9 | import Loading from "@/components/Loading/Loading"; 10 | 11 | const ForgotPassword = () => { 12 | // ------------------------ State Start ---------------------- 13 | const [message, setMessage] = useState(false); 14 | const [data, setData] = useState({ 15 | email: "", 16 | password: "", 17 | confirmPassword: "", 18 | }); 19 | const [validEmail, setValidEmail] = useState(false); 20 | const [validPassword, setValidPassword] = useState(false); 21 | const [validConfirmPassword, setValidConfirmPassword] = useState(false); 22 | const [showPassword, setShowPassword] = useState(false); 23 | const [showConfirmPassword, setShowConfirmPassword] = useState(false); 24 | const [loading, setLoading] = useState(false); 25 | 26 | // --------------------- State End ---------------------- 27 | const navigate = useNavigate(); 28 | const handleInputBox = (e) => { 29 | setData({ ...data, [e.target.name]: e.target.value }); 30 | }; 31 | const handleResetPassoword = () => { 32 | // =================== Email validation =================== 33 | const emailPattern = 34 | /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; 35 | if (emailPattern.test(data.email)) { 36 | setValidEmail(false); 37 | } else { 38 | setValidEmail(true); 39 | } 40 | // ==================== Password validation ===================== 41 | if (data.password.length >= 8) { 42 | setValidPassword(false); 43 | } else { 44 | setValidPassword(true); 45 | } 46 | 47 | // =================== Confirm Password validation ================= 48 | if (data.password === data.confirmPassword) { 49 | setValidConfirmPassword(false); 50 | } else { 51 | setValidConfirmPassword(true); 52 | } 53 | if ( 54 | validEmail === false && 55 | !(data.email === "") && 56 | !(data.password === "") && 57 | validPassword === false && 58 | !(data.confirmPassword === "") && 59 | validConfirmPassword === false 60 | ) { 61 | // console.log(data); 62 | handleApiCalling(data); 63 | } 64 | }; 65 | 66 | const handleApiCalling = async (data) => { 67 | try { 68 | setLoading(true); 69 | const response = await axios.put(apiRoutes.resetpasswordURI, data, { 70 | headers: { 71 | "Content-Type": "application/json", 72 | }, 73 | }); 74 | // console.log(response.data); 75 | setLoading(false); 76 | setMessage(false); 77 | navigate("/user/profile"); 78 | } catch (error) { 79 | setLoading(false); 80 | // console.log(error.response.data.message); 81 | if (error.response.data.message === "user not found") { 82 | setMessage(true); 83 | } 84 | } 85 | }; 86 | return ( 87 | <> 88 | {loading ? : ""} 89 |
90 |
91 |
92 | logo 93 |
94 |

Reset Your Password

95 |
e.preventDefault()} 99 | > 100 | {message ? ( 101 |
102 | User doesn't exist 103 |
104 | ) : null} 105 |
106 | {/* ===================== Email ======================= */} 107 |
108 | 118 | {validEmail ? ( 119 | 120 | Invalid Email Addresss 121 | 122 | ) : null} 123 |
124 |
125 | 126 |
127 | {/* ===================== New Password ======================= */} 128 |
129 | 156 | {validPassword ? ( 157 | 158 | *Enter minimum 8 charecter 159 | 160 | ) : null} 161 |
162 |
163 |
164 | {/* ===================== Confirm Password ======================= */} 165 |
166 | 195 | {validConfirmPassword ? ( 196 | 197 | *Enter Same Password 198 | 199 | ) : null} 200 |
201 |
202 | {/*
203 | 209 | 213 | Forgot password? 214 | 215 |
*/} 216 | {/*
*/} 217 |
218 | 224 |
225 | {/*
*/} 226 | {/*
*/} 227 |
228 | 229 | {/* Don’t have an account yet?{" "} */} 230 | 231 | {/* */} 232 | {/* Sign up here */} 233 | Back 234 | {/* Return to Profile Page */} 235 | 236 | 237 |
238 |
239 |
240 |
241 | 242 | ); 243 | }; 244 | 245 | export default ForgotPassword; 246 | -------------------------------------------------------------------------------- /frontend/public/notebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 165 | 166 | 167 | --------------------------------------------------------------------------------