├── 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 | handleDeleteBtn()}
30 | className="bg-gray-300 py-1 px-3 rounded shadow-mds shadow-gray-400 hover:bg-blue-400 hover:text-white hover:shadow-none"
31 | >
32 | Cancel
33 |
34 |
38 | Delete
39 |
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 |
39 |
40 |
41 |
View
42 | handleChangeGrid("list")}
44 | className=" w-full flex items-center border border-green-800 gap-x-2 p-2 text-xl text-green-800 hover:bg-green-800 hover:text-white"
45 | >
46 |
47 | List
48 |
49 | handleChangeGrid("grid")}
51 | className=" w-full flex items-center border border-green-800 gap-x-2 p-2 text-xl text-green-800 hover:bg-green-800 hover:text-white"
52 | >
53 | Grid
54 |
55 | handleChangeGrid("largeGrid")}
57 | className=" w-full flex items-center border border-green-800 gap-x-2 p-2 text-xl text-green-800 hover:bg-green-800 hover:text-white"
58 | >
59 |
60 | Large gird
61 |
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 | handleThemeBackBtn()}
27 | className="self-end bg-red-500 px-3 py-2 text-white rounded hover:bg-red-700"
28 | >
29 |
30 |
31 |
Choose Color
32 |
33 |
34 | handleNotesThemeColorBtn("bg-green-100")}
36 | className="bg-green-100 py-5 hover:border hover:border-gray-700"
37 | >
38 |
39 | handleNotesThemeColorBtn("bg-pink-100")}
41 | className="bg-pink-100 py-5 hover:border hover:border-gray-700"
42 | >
43 |
44 | handleNotesThemeColorBtn("bg-blue-100")}
46 | className="bg-blue-100 py-5 hover:border hover:border-gray-700"
47 | >
48 |
49 | handleNotesThemeColorBtn("bg-gray-100")}
51 | className="bg-gray-100 py-5 hover:border hover:border-gray-700"
52 | >
53 |
54 | handleNotesThemeColorBtn("bg-yellow-100")}
56 | className="bg-yellow-100 py-5 hover:border hover:border-gray-700"
57 | >
58 |
59 | handleNotesThemeColorBtn("bg-red-100")}
61 | className="bg-red-100 py-5 hover:border hover:border-gray-700"
62 | >
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 | 
113 |
114 |
115 | ## Screenshots
116 |
117 | 
118 |
119 | 
120 |
121 | 
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 | [](https://jabedalimollah.netlify.app/)
141 |
142 | [](https://www.linkedin.com/in/jabedalimollah/)
143 |
144 | [](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 |
80 | {/* {switchChecked ? ( */}
81 | {passwordDetails.isPasswordProtected ? (
82 |
83 | ) : (
84 |
85 | )}
86 |
87 |
88 |
89 |
99 |
100 |
101 |
111 | {passwordDetails.password ===
112 | passwordDetails.confirmNotesPassword ? (
113 | ""
114 | ) : (
115 | *Password are not matching
116 | )}
117 |
118 |
119 | handlelockBtn()}
121 | className="bg-red-400 text-white px-3 py-1 rounded shadow-xl hover:bg-red-700"
122 | >
123 | Back
124 |
125 |
129 | Save
130 |
131 |
132 |
133 | {/*
Airplane Mode */}
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 | handleImageChangeBtn()}
32 | className={`w-5/12 py-2 bg-red-600 rounded text-white hover:bg-red-700`}
33 | >
34 | Cancel
35 |
36 |
37 |
41 | Change Image
42 |
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 | handleChangeImageBtn()}
112 | className={`bg-red-600 hover:bg-red-700 shadow-2xl w-4/12 py-2 text-white rounded`}
113 | >
114 | Cancel
115 |
116 |
120 | Save
121 |
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 |
148 | View
149 |
150 |
151 |
154 | Change Image
155 |
156 |
157 |
160 | Default Picture
161 |
162 |
163 | handleImageChangeBtn()}
165 | className={`border-2 border-red-600 p-2 w-10/12 rounded text-red-800 hover:bg-red-800 hover:text-white font-bold`}
166 | >
167 | Cancel
168 |
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 |
96 |
97 |
98 |
99 |
100 |
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 | handleBackBtn()}
63 | >
64 | Permanently Delete Account
65 |
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 |
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 |
88 |
89 |
Sign in to your account
90 |
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 |
46 |
47 | dispatch(menuToggle())}
50 | >
51 | {menuBtn ? : }
52 |
53 |
54 | {/*
*/}
55 | {/* ================================================================= */}
56 |
61 |
62 | {auth ? (
63 | <>
64 |
65 |
66 |
67 |
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 |
83 | Add Task
84 |
85 | setListData([])}
87 | className="bg-red-700 hover:bg-red-800 text-white py-1 px-3 rounded"
88 | >
89 | Delete All
90 |
91 |
92 |
93 | {!(listData.length === 0) ? (
94 | listData.map((item, index) => (
95 |
99 |
100 | handleCheckBox(index)}
106 | />
107 |
108 | {item}
109 |
110 |
111 |
112 |
113 | updateData(item, index)}
116 | >
117 |
118 | {/* Edit */}
119 |
120 | handleDeleteBtn(index)}
123 | >
124 |
125 | {/* Delete */}
126 |
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 |
190 | Close
191 |
192 |
196 | Add
197 |
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 |
244 | Close
245 |
246 |
250 | Add
251 |
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 |
128 |
129 |
130 |
131 |
132 |
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 |
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 |
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 |
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 |
109 | Email
110 |
117 |
118 | {validEmail ? (
119 |
120 | Invalid Email Addresss
121 |
122 | ) : null}
123 |
124 |
125 |
126 |
127 | {/* ===================== New Password ======================= */}
128 |
129 |
130 | New Password
131 |
132 |
139 | {showPassword ? (
140 | setShowPassword(false)}
143 | >
144 |
145 |
146 | ) : (
147 | setShowPassword(true)}
150 | >
151 |
152 |
153 | )}
154 |
155 |
156 | {validPassword ? (
157 |
158 | *Enter minimum 8 charecter
159 |
160 | ) : null}
161 |
162 |
163 |
164 | {/* ===================== Confirm Password ======================= */}
165 |
166 |
167 |
168 | Confirm Password
169 |
170 |
171 |
178 | {showConfirmPassword ? (
179 | setShowConfirmPassword(false)}
182 | >
183 |
184 |
185 | ) : (
186 | setShowConfirmPassword(true)}
189 | >
190 |
191 |
192 | )}
193 |
194 |
195 | {validConfirmPassword ? (
196 |
197 | *Enter Same Password
198 |
199 | ) : null}
200 |
201 |
202 | {/*
203 |
204 |
205 |
206 | Remember me
207 |
208 |
209 |
213 | Forgot password?
214 |
215 |
*/}
216 | {/* */}
217 |
218 |
222 | Reset Password
223 |
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 |
--------------------------------------------------------------------------------