├── README.md
├── admin-panel
├── .env.example
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .prettierignore
├── .prettierrc
├── README.md
├── index.html
├── jsconfig.json
├── package-lock.json
├── package.json
├── public
│ ├── _redirects
│ ├── assets
│ │ ├── background
│ │ │ ├── overlay_1.svg
│ │ │ ├── overlay_2.jpg
│ │ │ ├── overlay_3.jpg
│ │ │ └── overlay_4.jpg
│ │ ├── icons
│ │ │ ├── glass
│ │ │ │ ├── ic_glass_bag.png
│ │ │ │ ├── ic_glass_buy.png
│ │ │ │ ├── ic_glass_message.png
│ │ │ │ └── ic_glass_users.png
│ │ │ ├── ic_flag_de.svg
│ │ │ ├── ic_flag_en.svg
│ │ │ ├── ic_flag_fr.svg
│ │ │ ├── ic_notification_chat.svg
│ │ │ ├── ic_notification_mail.svg
│ │ │ ├── ic_notification_package.svg
│ │ │ ├── ic_notification_shipping.svg
│ │ │ ├── navbar
│ │ │ │ ├── ic_analytics.svg
│ │ │ │ ├── ic_blog.svg
│ │ │ │ ├── ic_cart.svg
│ │ │ │ ├── ic_disabled.svg
│ │ │ │ ├── ic_lock.svg
│ │ │ │ └── ic_user.svg
│ │ │ └── shape-avatar.svg
│ │ ├── illustrations
│ │ │ ├── illustration_404.svg
│ │ │ ├── illustration_avatar.png
│ │ │ └── illustration_login.png
│ │ ├── images
│ │ │ ├── avatars
│ │ │ │ ├── avatar_1.jpg
│ │ │ │ ├── avatar_10.jpg
│ │ │ │ ├── avatar_11.jpg
│ │ │ │ ├── avatar_12.jpg
│ │ │ │ ├── avatar_13.jpg
│ │ │ │ ├── avatar_14.jpg
│ │ │ │ ├── avatar_15.jpg
│ │ │ │ ├── avatar_16.jpg
│ │ │ │ ├── avatar_17.jpg
│ │ │ │ ├── avatar_18.jpg
│ │ │ │ ├── avatar_19.jpg
│ │ │ │ ├── avatar_2.jpg
│ │ │ │ ├── avatar_20.jpg
│ │ │ │ ├── avatar_21.jpg
│ │ │ │ ├── avatar_22.jpg
│ │ │ │ ├── avatar_23.jpg
│ │ │ │ ├── avatar_24.jpg
│ │ │ │ ├── avatar_25.jpg
│ │ │ │ ├── avatar_3.jpg
│ │ │ │ ├── avatar_4.jpg
│ │ │ │ ├── avatar_5.jpg
│ │ │ │ ├── avatar_6.jpg
│ │ │ │ ├── avatar_7.jpg
│ │ │ │ ├── avatar_8.jpg
│ │ │ │ └── avatar_9.jpg
│ │ │ ├── covers
│ │ │ │ ├── cover_1.jpg
│ │ │ │ ├── cover_10.jpg
│ │ │ │ ├── cover_11.jpg
│ │ │ │ ├── cover_12.jpg
│ │ │ │ ├── cover_13.jpg
│ │ │ │ ├── cover_14.jpg
│ │ │ │ ├── cover_15.jpg
│ │ │ │ ├── cover_16.jpg
│ │ │ │ ├── cover_17.jpg
│ │ │ │ ├── cover_18.jpg
│ │ │ │ ├── cover_19.jpg
│ │ │ │ ├── cover_2.jpg
│ │ │ │ ├── cover_20.jpg
│ │ │ │ ├── cover_21.jpg
│ │ │ │ ├── cover_22.jpg
│ │ │ │ ├── cover_23.jpg
│ │ │ │ ├── cover_24.jpg
│ │ │ │ ├── cover_3.jpg
│ │ │ │ ├── cover_4.jpg
│ │ │ │ ├── cover_5.jpg
│ │ │ │ ├── cover_6.jpg
│ │ │ │ ├── cover_7.jpg
│ │ │ │ ├── cover_8.jpg
│ │ │ │ └── cover_9.jpg
│ │ │ └── products
│ │ │ │ ├── product_1.jpg
│ │ │ │ ├── product_10.jpg
│ │ │ │ ├── product_11.jpg
│ │ │ │ ├── product_12.jpg
│ │ │ │ ├── product_13.jpg
│ │ │ │ ├── product_14.jpg
│ │ │ │ ├── product_15.jpg
│ │ │ │ ├── product_16.jpg
│ │ │ │ ├── product_17.jpg
│ │ │ │ ├── product_18.jpg
│ │ │ │ ├── product_19.jpg
│ │ │ │ ├── product_2.jpg
│ │ │ │ ├── product_20.jpg
│ │ │ │ ├── product_21.jpg
│ │ │ │ ├── product_22.jpg
│ │ │ │ ├── product_23.jpg
│ │ │ │ ├── product_24.jpg
│ │ │ │ ├── product_3.jpg
│ │ │ │ ├── product_4.jpg
│ │ │ │ ├── product_5.jpg
│ │ │ │ ├── product_6.jpg
│ │ │ │ ├── product_7.jpg
│ │ │ │ ├── product_8.jpg
│ │ │ │ └── product_9.jpg
│ │ ├── logo.svg
│ │ ├── placeholder.svg
│ │ ├── preview.jpg
│ │ └── transparent.png
│ └── manifest.json
├── src
│ ├── _mock
│ │ ├── account.js
│ │ ├── blog.js
│ │ ├── products.js
│ │ └── user.js
│ ├── app.jsx
│ ├── components
│ │ ├── chart
│ │ │ ├── chart.js
│ │ │ ├── index.js
│ │ │ └── use-chart.js
│ │ ├── color-utils
│ │ │ ├── color-picker.jsx
│ │ │ ├── color-preview.jsx
│ │ │ └── index.js
│ │ ├── iconify
│ │ │ ├── iconify.jsx
│ │ │ └── index.js
│ │ ├── label
│ │ │ ├── index.js
│ │ │ ├── label.jsx
│ │ │ └── styles.js
│ │ ├── logo
│ │ │ ├── index.js
│ │ │ └── logo.jsx
│ │ ├── scrollbar
│ │ │ ├── index.js
│ │ │ ├── scrollbar.jsx
│ │ │ └── styles.js
│ │ └── svg-color
│ │ │ ├── index.js
│ │ │ └── svg-color.jsx
│ ├── global.css
│ ├── hooks
│ │ ├── use-responsive.js
│ │ └── use-scroll-to-top.js
│ ├── layouts
│ │ └── dashboard
│ │ │ ├── common
│ │ │ ├── account-popover.jsx
│ │ │ ├── language-popover.jsx
│ │ │ ├── notifications-popover.jsx
│ │ │ └── searchbar.jsx
│ │ │ ├── config-layout.js
│ │ │ ├── config-navigation.jsx
│ │ │ ├── header.jsx
│ │ │ ├── index.jsx
│ │ │ ├── main.jsx
│ │ │ └── nav.jsx
│ ├── main.jsx
│ ├── pages
│ │ ├── app.jsx
│ │ ├── login.jsx
│ │ └── user.jsx
│ ├── providers
│ │ └── AuthProvider.jsx
│ ├── routes
│ │ ├── components
│ │ │ ├── index.js
│ │ │ └── router-link.jsx
│ │ ├── hooks
│ │ │ ├── index.js
│ │ │ ├── use-pathname.js
│ │ │ └── use-router.js
│ │ └── sections.jsx
│ ├── sections
│ │ ├── login
│ │ │ ├── index.js
│ │ │ └── login-view.jsx
│ │ ├── overview
│ │ │ ├── app-conversion-rates.jsx
│ │ │ ├── app-current-subject.jsx
│ │ │ ├── app-current-visits.jsx
│ │ │ ├── app-news-update.jsx
│ │ │ ├── app-order-timeline.jsx
│ │ │ ├── app-tasks.jsx
│ │ │ ├── app-traffic-by-site.jsx
│ │ │ ├── app-website-visits.jsx
│ │ │ ├── app-widget-summary.jsx
│ │ │ └── view
│ │ │ │ ├── app-view.jsx
│ │ │ │ └── index.js
│ │ └── user
│ │ │ ├── table-empty-rows.jsx
│ │ │ ├── table-no-data.jsx
│ │ │ ├── user-table-head.jsx
│ │ │ ├── user-table-row.jsx
│ │ │ ├── user-table-toolbar.jsx
│ │ │ ├── utils.js
│ │ │ └── view
│ │ │ ├── index.js
│ │ │ └── user-view.jsx
│ ├── theme
│ │ ├── css.js
│ │ ├── custom-shadows.js
│ │ ├── index.jsx
│ │ ├── overrides.js
│ │ ├── palette.js
│ │ ├── shadows.js
│ │ └── typography.js
│ └── utils
│ │ ├── format-number.js
│ │ └── format-time.js
├── vercel.json
├── vite.config.js
└── yarn.lock
├── client
├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── app
│ ├── (auth)
│ │ ├── layout.tsx
│ │ ├── reset-password
│ │ │ └── page.tsx
│ │ ├── signin
│ │ │ └── page.tsx
│ │ └── signup
│ │ │ └── page.tsx
│ ├── (default)
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── css
│ │ ├── additional-styles
│ │ │ ├── range-slider.css
│ │ │ ├── theme.css
│ │ │ ├── toggle-switch.css
│ │ │ └── utility-patterns.css
│ │ └── style.css
│ ├── layout.tsx
│ └── notes
│ │ └── page.tsx
├── components
│ ├── Features.tsx
│ ├── FeaturesBlocks.tsx
│ ├── Hero.tsx
│ ├── ModalVideo.tsx
│ ├── Newsletter.tsx
│ ├── Testimonials.tsx
│ ├── notebook
│ │ ├── AddNote.tsx
│ │ └── DeleteNote.tsx
│ └── ui
│ │ ├── Footer.tsx
│ │ ├── Header.tsx
│ │ ├── Logo.tsx
│ │ ├── MobileMenu.tsx
│ │ ├── SIgnInForm.tsx
│ │ └── SIgnUpForm.tsx
├── constants
│ └── index.ts
├── middleware.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── favicon.ico
│ ├── images
│ │ ├── anime.jpeg
│ │ ├── feature1.jpeg
│ │ ├── feature2.jpeg
│ │ ├── feature3.jpeg
│ │ └── hero-image.png
│ └── videos
│ │ └── video.mp4
├── tailwind.config.js
├── tsconfig.json
└── utils
│ └── notes.ts
├── makefile
└── server
├── .env.example
├── .gitignore
├── README.md
├── cmd
└── main.go
├── controllers
├── auth.go
└── notes.go
├── database
└── connection.go
├── go.mod
├── go.sum
├── middleware
├── auth.go
└── role.go
├── models
├── note.go
└── user.go
└── routes
├── admin.go
├── auth.go
└── notes.go
/README.md:
--------------------------------------------------------------------------------
1 |
QuickNotes 📜
2 |
3 |
4 | Welcome to QuickNotes - your ultimate tool for note-taking and management. This project consists of the backend server, frontend client, and admin panel, all bundled together for seamless development and deployment.
5 |
6 |
7 | ## 📂 Folder Structure
8 |
9 | - **`server/`**: Contains the backend server written in GoLang. Handles authentication, database manipulation, and serves API endpoints.
10 | - **`client/`**: Houses the frontend client written in Next.js. Provides a user-friendly interface for note-taking and management.
11 | - **`admin-panel/`**: Includes the admin panel frontend built with React.js and Material-UI. Allows admin users to monitor users and their notes.
12 |
13 | ## 🛠️ Technologies Used
14 |
15 | ### Backend Server (server/)
16 | - 🐍 Golang
17 | - 🔑 jwt-go for JWT token-based authentication and authorization
18 | - 🔌 Fiber for web server implementation
19 | - 🛠️ Gorm ORM for database manipulation
20 |
21 | ### Frontend Client (client/)
22 | - 🌐 TS + NextJs
23 | - 🎨 Tailwind CSS for styling
24 | - 🌟 AOS for animations
25 | - 🚀 Headless UI for ready components
26 |
27 | ### Admin Panel (admin-panel/)
28 | - 🌐 JS + ReactJs
29 | - 🎨 Material-UI library for UI components
30 | - 📊 React-apexCharts for chart and visualization
31 |
32 | ## 🚀 How to Use
33 |
34 | 1. 📝 Clone the repository by running:
35 | ```
36 | git clone https://github.com/mutasim77/quick-notes.git
37 | ```
38 |
39 | 2. 📂 Navigate to the project directory:
40 | ```
41 | cd quick-notes
42 | ```
43 |
44 | 3. 📦 Install dependencies for each component:
45 | ```
46 | make install
47 | ```
48 |
49 | 3. ⚙️ Set up the `.env` files for server and client based on their respective `.env.example` files.
50 |
51 | 5. 🏃♂️ Run each component:
52 | ```
53 | make run-server
54 | make run-client
55 | make run-admin-panel
56 | ```
57 |
58 | 5. 🌐 Access the client and admin panel in your browser at the provided URLs.
59 |
60 | > [!IMPORTANT]
61 | > The project is currently a work in progress and actively under development. That's why, for the time being, I haven't deployed it anywhere (even though I briefly deployed it in development mode and then removed it). Perhaps later, I will consider deploying it on AWS Cloud.
62 |
63 |
64 | ## 📸 Demo
65 | Here, I will provide some screenshots to give you a glimpse of how the project looks. Since it's not deployed yet, viewing these screenshots will provide an overview of the project's user interface and functionality.
66 |
67 | ### Home Page 🧇
68 | 1.
69 |
70 | 2.
71 |
72 |
73 | ### Login Page 🥯
74 | 1.
75 |
76 |
77 | ### Notes Page 🍟
78 | 1.
79 |
80 |
81 | ### Admin Panel 🌯
82 | 1.
83 | 
84 |
85 |
86 | > [!NOTE]
87 | > Thank you for taking the time to explore this project. If you found it interesting or useful, I would greatly appreciate it if you could give it a star ⭐. If you have any questions, feedback, or suggestions regarding the project, please feel free to open an issue.
88 |
89 | Happy coding! 🚀
90 |
--------------------------------------------------------------------------------
/admin-panel/.env.example:
--------------------------------------------------------------------------------
1 | VITE_API_URL=
--------------------------------------------------------------------------------
/admin-panel/.eslintignore:
--------------------------------------------------------------------------------
1 | // .eslintignore
2 | build/*
3 | dist/*
4 | public/*
5 | **/out/*
6 | **/node_modules/*
7 |
8 | **/.next/*
9 | next.config.js
10 |
11 | vite.config.js
12 | vite.config.ts
13 |
14 | src/reportWebVitals.js
15 | src/service-worker.js
16 | src/serviceWorkerRegistration.js
17 | src/setupTests.js
18 |
19 | src/reportWebVitals.ts
20 | src/service-worker.ts
21 | src/serviceWorkerRegistration.ts
22 | src/setupTests.ts
23 |
--------------------------------------------------------------------------------
/admin-panel/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "ecmaVersion": "latest",
4 | "sourceType": "module",
5 | "ecmaFeatures": {
6 | "jsx": true
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/admin-panel/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-detectable=false
2 | *.jsx linguist-detectable=false
3 | *.css linguist-detectable=false
4 | *.html linguist-detectable=false
5 |
6 |
--------------------------------------------------------------------------------
/admin-panel/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # testing
9 | coverage
10 |
11 | # production
12 | .next
13 | .swc
14 | _static
15 | out
16 | dist
17 | build
18 |
19 | # environment variables
20 | .env
21 |
22 | # misc
23 | .DS_Store
24 | .vercel
25 | .netlify
26 | .unimportedrc.json
27 | tsconfig.tsbuildinfo
28 | .vscode
29 |
30 | npm-debug.log*
31 | yarn-debug.log*
32 | yarn-error.log*
33 |
--------------------------------------------------------------------------------
/admin-panel/.prettierignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | public/*
4 | **/out/*
5 | **/.next/*
6 | **/node_modules/*
7 |
8 | package-lock.json
9 | yarn.lock
10 |
--------------------------------------------------------------------------------
/admin-panel/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "tabWidth": 2
6 | }
7 |
--------------------------------------------------------------------------------
/admin-panel/README.md:
--------------------------------------------------------------------------------
1 | 👨💼 QuickNotes Admin Panel 📊
2 |
3 |
4 | Admin panel for QuickNotes app. Monitor users and their notes. Please note that the admin panel is still under active development, and some features may not be completed yet.
5 |
6 |
7 | ## 🛠️ Features
8 |
9 | - 🔒 Authentication: Only admin has access to the dashboard.
10 | - 💻 Responsive and user-friendly UI.
11 | - 🕵️♂️ Monitoring: View users' information and their notes.
12 |
13 |
14 | ## 💻 Technologies Used
15 |
16 | - 🌐 JavaScript + React.js.
17 | - 🎨 MUI library for cool UI and ready components.
18 | - 📊 React-ApexCharts for charts and visualization. (Note: The data is currently mock data and will be replaced with actual data from the database in the future)
19 |
20 |
21 | ## ℹ️ How to Use
22 |
23 | 1. 📝 Set up your `.env` file with required configurations:
24 | ```
25 | VITE_API_URL=
26 | ```
27 |
28 | 2. 📦 Install dependencies by running:
29 | ```
30 | yarn
31 | ```
32 |
33 | 3. 🏃♂️ Run the admin panel in development mode by executing:
34 | ```
35 | yarn run dev
36 | ```
37 |
38 | 4. 🌐 Access the admin panel in your browser at the provided URL.
39 |
40 | > Thank you for showing interest in this project. Happy coding! 🎉
41 |
--------------------------------------------------------------------------------
/admin-panel/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 | QuickNotes Admin Panel
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/admin-panel/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "."
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/admin-panel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@minimal/material-kit-react",
3 | "author": "minimals.cc",
4 | "licence": "MIT",
5 | "version": "1.8.0",
6 | "private": false,
7 | "scripts": {
8 | "dev": "vite",
9 | "start": "vite preview",
10 | "build": "vite build",
11 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
12 | "lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx}\"",
13 | "prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
14 | "rm:all": "rm -rf node_modules .next out dist build",
15 | "re:start": "yarn rm:all && yarn install && yarn dev",
16 | "re:build": "yarn rm:all && yarn install && yarn build",
17 | "re:build-npm": "npm run rm:all && npm install && npm run build",
18 | "dev:host": "vite --host",
19 | "vite:start": "vite",
20 | "vite:build": "vite build"
21 | },
22 | "dependencies": {
23 | "@emotion/react": "^11.11.1",
24 | "@emotion/styled": "^11.11.0",
25 | "@faker-js/faker": "^8.1.0",
26 | "@iconify/react": "^4.1.1",
27 | "@mui/lab": "^5.0.0-alpha.147",
28 | "@mui/material": "^5.14.12",
29 | "apexcharts": "^3.43.0",
30 | "date-fns": "^2.30.0",
31 | "dotenv": "^16.4.5",
32 | "history": "^5.3.0",
33 | "lodash": "^4.17.21",
34 | "numeral": "^2.0.6",
35 | "prop-types": "^15.8.1",
36 | "react": "^18.2.0",
37 | "react-apexcharts": "^1.4.1",
38 | "react-dom": "^18.2.0",
39 | "react-helmet-async": "^1.3.0",
40 | "react-hook-form": "^7.47.0",
41 | "react-router-dom": "^6.16.0",
42 | "simplebar-react": "^3.2.4"
43 | },
44 | "devDependencies": {
45 | "@vitejs/plugin-react-swc": "^3.4.0",
46 | "eslint": "^8.51.0",
47 | "eslint-config-airbnb": "^19.0.4",
48 | "eslint-config-prettier": "^9.0.0",
49 | "eslint-import-resolver-alias": "^1.1.2",
50 | "eslint-plugin-import": "^2.28.1",
51 | "eslint-plugin-jsx-a11y": "^6.7.1",
52 | "eslint-plugin-perfectionist": "^2.1.0",
53 | "eslint-plugin-prettier": "^5.0.0",
54 | "eslint-plugin-react": "^7.33.2",
55 | "eslint-plugin-react-hooks": "^4.6.0",
56 | "eslint-plugin-unused-imports": "^3.0.0",
57 | "prettier": "^3.0.3",
58 | "vite": "^4.4.11",
59 | "vite-plugin-checker": "^0.6.2"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/admin-panel/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/admin-panel/public/assets/background/overlay_1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/background/overlay_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/background/overlay_2.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/background/overlay_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/background/overlay_3.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/background/overlay_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/background/overlay_4.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/glass/ic_glass_bag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/icons/glass/ic_glass_bag.png
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/glass/ic_glass_buy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/icons/glass/ic_glass_buy.png
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/glass/ic_glass_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/icons/glass/ic_glass_message.png
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/glass/ic_glass_users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/icons/glass/ic_glass_users.png
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_flag_de.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_flag_en.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_flag_fr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_notification_chat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_notification_mail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_notification_package.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/ic_notification_shipping.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/navbar/ic_analytics.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/navbar/ic_blog.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/navbar/ic_cart.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/navbar/ic_disabled.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/navbar/ic_lock.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/navbar/ic_user.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/icons/shape-avatar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/illustrations/illustration_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/illustrations/illustration_avatar.png
--------------------------------------------------------------------------------
/admin-panel/public/assets/illustrations/illustration_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/illustrations/illustration_login.png
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_1.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_10.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_11.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_12.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_13.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_14.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_15.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_16.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_17.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_18.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_19.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_2.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_20.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_21.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_22.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_22.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_23.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_23.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_24.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_24.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_25.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_25.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_3.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_4.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_5.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_6.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_7.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_8.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/avatars/avatar_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/avatars/avatar_9.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_1.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_10.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_11.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_12.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_13.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_14.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_15.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_16.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_17.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_18.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_19.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_2.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_20.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_21.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_22.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_22.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_23.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_23.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_24.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_24.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_3.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_4.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_5.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_6.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_7.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_8.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/covers/cover_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/covers/cover_9.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_1.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_10.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_11.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_12.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_13.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_14.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_15.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_16.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_17.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_18.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_19.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_2.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_20.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_21.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_22.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_22.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_23.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_23.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_24.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_24.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_3.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_4.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_5.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_6.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_7.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_8.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/images/products/product_9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/images/products/product_9.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/placeholder.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin-panel/public/assets/preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/preview.jpg
--------------------------------------------------------------------------------
/admin-panel/public/assets/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/admin-panel/public/assets/transparent.png
--------------------------------------------------------------------------------
/admin-panel/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Minimal App",
3 | "name": "QuickNotes Admin Panel",
4 | "icons": [
5 | {
6 | "src": "favicon/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "favicon/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "start_url": ".",
17 | "display": "standalone",
18 | "theme_color": "#000000",
19 | "background_color": "#ffffff"
20 | }
--------------------------------------------------------------------------------
/admin-panel/src/_mock/account.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export const account = {
4 | displayName: 'Mutasim',
5 | email: 'mutasmi@gmail.com',
6 | photoURL: '/assets/images/avatars/avatar_15.jpg',
7 | };
8 |
--------------------------------------------------------------------------------
/admin-panel/src/_mock/blog.js:
--------------------------------------------------------------------------------
1 | import { faker } from '@faker-js/faker';
2 |
3 | // ----------------------------------------------------------------------
4 |
5 | const POST_TITLES = [
6 | 'Whiteboard Templates By Industry Leaders',
7 | 'Tesla Cybertruck-inspired camper trailer for Tesla fans who can’t just wait for the truck!',
8 | 'Designify Agency Landing Page Design',
9 | '✨What is Done is Done ✨',
10 | 'Fresh Prince',
11 | 'Six Socks Studio',
12 | 'vincenzo de cotiis’ crossing over showcases a research on contamination',
13 | 'Simple, Great Looking Animations in Your Project | Video Tutorial',
14 | '40 Free Serif Fonts for Digital Designers',
15 | 'Examining the Evolution of the Typical Web Design Client',
16 | 'Katie Griffin loves making that homey art',
17 | 'The American Dream retold through mid-century railroad graphics',
18 | 'Illustration System Design',
19 | 'CarZio-Delivery Driver App SignIn/SignUp',
20 | 'How to create a client-serverless Jamstack app using Netlify, Gatsby and Fauna',
21 | 'Tylko Organise effortlessly -3D & Motion Design',
22 | 'RAYO ?? A expanded visual arts festival identity',
23 | 'Anthony Burrill and Wired mag’s Andrew Diprose discuss how they made January’s Change Everything cover',
24 | 'Inside the Mind of Samuel Day',
25 | 'Portfolio Review: Is This Portfolio Too Creative?',
26 | 'Akkers van Margraten',
27 | 'Gradient Ticket icon',
28 | 'Here’s a Dyson motorcycle concept that doesn’t ‘suck’!',
29 | 'How to Animate a SVG with border-image',
30 | ];
31 |
32 | export const posts = [...Array(23)].map((_, index) => ({
33 | id: faker.string.uuid(),
34 | cover: `/assets/images/covers/cover_${index + 1}.jpg`,
35 | title: POST_TITLES[index + 1],
36 | createdAt: faker.date.past(),
37 | view: faker.number.int(99999),
38 | comment: faker.number.int(99999),
39 | share: faker.number.int(99999),
40 | favorite: faker.number.int(99999),
41 | author: {
42 | name: faker.person.fullName(),
43 | avatarUrl: `/assets/images/avatars/avatar_${index + 1}.jpg`,
44 | },
45 | }));
46 |
--------------------------------------------------------------------------------
/admin-panel/src/_mock/products.js:
--------------------------------------------------------------------------------
1 | import { sample } from 'lodash';
2 | import { faker } from '@faker-js/faker';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | const PRODUCT_NAME = [
7 | 'Nike Air Force 1 NDESTRUKT',
8 | 'Nike Space Hippie 04',
9 | 'Nike Air Zoom Pegasus 37 A.I.R. Chaz Bear',
10 | 'Nike Blazer Low 77 Vintage',
11 | 'Nike ZoomX SuperRep Surge',
12 | 'Zoom Freak 2',
13 | 'Nike Air Max Zephyr',
14 | 'Jordan Delta',
15 | 'Air Jordan XXXV PF',
16 | 'Nike Waffle Racer Crater',
17 | 'Kyrie 7 EP Sisterhood',
18 | 'Nike Air Zoom BB NXT',
19 | 'Nike Air Force 1 07 LX',
20 | 'Nike Air Force 1 Shadow SE',
21 | 'Nike Air Zoom Tempo NEXT%',
22 | 'Nike DBreak-Type',
23 | 'Nike Air Max Up',
24 | 'Nike Air Max 270 React ENG',
25 | 'NikeCourt Royale',
26 | 'Nike Air Zoom Pegasus 37 Premium',
27 | 'Nike Air Zoom SuperRep',
28 | 'NikeCourt Royale',
29 | 'Nike React Art3mis',
30 | 'Nike React Infinity Run Flyknit A.I.R. Chaz Bear',
31 | ];
32 | const PRODUCT_COLOR = [
33 | '#00AB55',
34 | '#000000',
35 | '#FFFFFF',
36 | '#FFC0CB',
37 | '#FF4842',
38 | '#1890FF',
39 | '#94D82D',
40 | '#FFC107',
41 | ];
42 |
43 | // ----------------------------------------------------------------------
44 |
45 | export const products = [...Array(24)].map((_, index) => {
46 | const setIndex = index + 1;
47 |
48 | return {
49 | id: faker.string.uuid(),
50 | cover: `/assets/images/products/product_${setIndex}.jpg`,
51 | name: PRODUCT_NAME[index],
52 | price: faker.number.int({ min: 4, max: 99, precision: 0.01 }),
53 | priceSale: setIndex % 3 ? null : faker.number.int({ min: 19, max: 29, precision: 0.01 }),
54 | colors:
55 | (setIndex === 1 && PRODUCT_COLOR.slice(0, 2)) ||
56 | (setIndex === 2 && PRODUCT_COLOR.slice(1, 3)) ||
57 | (setIndex === 3 && PRODUCT_COLOR.slice(2, 4)) ||
58 | (setIndex === 4 && PRODUCT_COLOR.slice(3, 6)) ||
59 | (setIndex === 23 && PRODUCT_COLOR.slice(4, 6)) ||
60 | (setIndex === 24 && PRODUCT_COLOR.slice(5, 6)) ||
61 | PRODUCT_COLOR,
62 | status: sample(['sale', 'new', '', '']),
63 | };
64 | });
65 |
--------------------------------------------------------------------------------
/admin-panel/src/_mock/user.js:
--------------------------------------------------------------------------------
1 | import { sample } from 'lodash';
2 | import { faker } from '@faker-js/faker';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export const users = [...Array(24)].map((_, index) => ({
7 | id: faker.string.uuid(),
8 | avatarUrl: `/assets/images/avatars/avatar_${index + 1}.jpg`,
9 | name: faker.person.fullName(),
10 | company: faker.company.name(),
11 | isVerified: faker.datatype.boolean(),
12 | status: sample(['active', 'banned']),
13 | role: sample([
14 | 'Leader',
15 | 'Hr Manager',
16 | 'UI Designer',
17 | 'UX Designer',
18 | 'UI/UX Designer',
19 | 'Project Manager',
20 | 'Backend Developer',
21 | 'Full Stack Designer',
22 | 'Front End Developer',
23 | 'Full Stack Developer',
24 | ]),
25 | }));
26 |
--------------------------------------------------------------------------------
/admin-panel/src/app.jsx:
--------------------------------------------------------------------------------
1 | import 'src/global.css';
2 |
3 | import { useScrollToTop } from 'src/hooks/use-scroll-to-top';
4 |
5 | import Router from 'src/routes/sections';
6 | import ThemeProvider from 'src/theme';
7 | import AuthProvider from './providers/AuthProvider';
8 |
9 | export default function App() {
10 | useScrollToTop();
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/admin-panel/src/components/chart/chart.js:
--------------------------------------------------------------------------------
1 | import { memo } from 'react';
2 | import ApexChart from 'react-apexcharts';
3 |
4 | import { alpha, styled } from '@mui/material/styles';
5 |
6 | import { bgBlur } from 'src/theme/css';
7 |
8 | // ----------------------------------------------------------------------
9 |
10 | const Chart = styled(ApexChart)(({ theme }) => ({
11 | '& .apexcharts-canvas': {
12 | // Tooltip
13 | '& .apexcharts-tooltip': {
14 | ...bgBlur({
15 | color: theme.palette.background.default,
16 | }),
17 | color: theme.palette.text.primary,
18 | boxShadow: theme.customShadows.dropdown,
19 | borderRadius: theme.shape.borderRadius * 1.25,
20 | '&.apexcharts-theme-light': {
21 | borderColor: 'transparent',
22 | ...bgBlur({
23 | color: theme.palette.background.default,
24 | }),
25 | },
26 | },
27 | '& .apexcharts-xaxistooltip': {
28 | ...bgBlur({
29 | color: theme.palette.background.default,
30 | }),
31 | borderColor: 'transparent',
32 | color: theme.palette.text.primary,
33 | boxShadow: theme.customShadows.dropdown,
34 | borderRadius: theme.shape.borderRadius * 1.25,
35 | '&:before': {
36 | borderBottomColor: alpha(theme.palette.grey[500], 0.24),
37 | },
38 | '&:after': {
39 | borderBottomColor: alpha(theme.palette.background.default, 0.8),
40 | },
41 | },
42 | '& .apexcharts-tooltip-title': {
43 | textAlign: 'center',
44 | fontWeight: theme.typography.fontWeightBold,
45 | backgroundColor: alpha(theme.palette.grey[500], 0.08),
46 | color: theme.palette.text[theme.palette.mode === 'light' ? 'secondary' : 'primary'],
47 | },
48 |
49 | // LEGEND
50 | '& .apexcharts-legend': {
51 | padding: 0,
52 | },
53 | '& .apexcharts-legend-series': {
54 | display: 'inline-flex !important',
55 | alignItems: 'center',
56 | },
57 | '& .apexcharts-legend-marker': {
58 | marginRight: 8,
59 | },
60 | '& .apexcharts-legend-text': {
61 | lineHeight: '18px',
62 | textTransform: 'capitalize',
63 | },
64 | },
65 | }));
66 |
67 | export default memo(Chart);
68 |
--------------------------------------------------------------------------------
/admin-panel/src/components/chart/index.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export { default } from './chart';
4 |
5 | export { default as useChart } from './use-chart';
6 |
--------------------------------------------------------------------------------
/admin-panel/src/components/color-utils/color-preview.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Stack from '@mui/material/Stack';
5 | import { alpha } from '@mui/material/styles';
6 |
7 | // ----------------------------------------------------------------------
8 |
9 | export default function ColorPreview({ colors, limit = 3, sx }) {
10 | const renderColors = colors.slice(0, limit);
11 |
12 | const remainingColor = colors.length - limit;
13 |
14 | return (
15 |
16 | {renderColors.map((color, index) => (
17 | `solid 2px ${theme.palette.background.paper}`,
26 | boxShadow: (theme) => `inset -1px 1px 2px ${alpha(theme.palette.common.black, 0.24)}`,
27 | }}
28 | />
29 | ))}
30 |
31 | {colors.length > limit && (
32 | {`+${remainingColor}`}
33 | )}
34 |
35 | );
36 | }
37 |
38 | ColorPreview.propTypes = {
39 | colors: PropTypes.arrayOf(PropTypes.string),
40 | limit: PropTypes.number,
41 | sx: PropTypes.object,
42 | };
43 |
--------------------------------------------------------------------------------
/admin-panel/src/components/color-utils/index.js:
--------------------------------------------------------------------------------
1 | export { default as ColorPicker } from './color-picker';
2 | export { default as ColorPreview } from './color-preview';
3 |
--------------------------------------------------------------------------------
/admin-panel/src/components/iconify/iconify.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { forwardRef } from 'react';
3 | import { Icon } from '@iconify/react';
4 |
5 | import Box from '@mui/material/Box';
6 |
7 | // ----------------------------------------------------------------------
8 |
9 | const Iconify = forwardRef(({ icon, width = 20, sx, ...other }, ref) => (
10 |
18 | ));
19 |
20 | Iconify.propTypes = {
21 | icon: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
22 | sx: PropTypes.object,
23 | width: PropTypes.number,
24 | };
25 |
26 | export default Iconify;
27 |
--------------------------------------------------------------------------------
/admin-panel/src/components/iconify/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './iconify';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/components/label/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './label';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/components/label/label.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { forwardRef } from 'react';
3 |
4 | import Box from '@mui/material/Box';
5 | import { useTheme } from '@mui/material/styles';
6 |
7 | import { StyledLabel } from './styles';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | const Label = forwardRef(
12 | ({ children, color = 'default', variant = 'soft', startIcon, endIcon, sx, ...other }, ref) => {
13 | const theme = useTheme();
14 |
15 | const iconStyles = {
16 | width: 16,
17 | height: 16,
18 | '& svg, img': { width: 1, height: 1, objectFit: 'cover' },
19 | };
20 |
21 | return (
22 |
34 | {startIcon && {startIcon} }
35 |
36 | {children}
37 |
38 | {endIcon && {endIcon} }
39 |
40 | );
41 | }
42 | );
43 |
44 | Label.propTypes = {
45 | children: PropTypes.node,
46 | endIcon: PropTypes.object,
47 | startIcon: PropTypes.object,
48 | sx: PropTypes.object,
49 | variant: PropTypes.oneOf(['filled', 'outlined', 'ghost', 'soft']),
50 | color: PropTypes.oneOf([
51 | 'default',
52 | 'primary',
53 | 'secondary',
54 | 'info',
55 | 'success',
56 | 'warning',
57 | 'error',
58 | ]),
59 | };
60 |
61 | export default Label;
62 |
--------------------------------------------------------------------------------
/admin-panel/src/components/label/styles.js:
--------------------------------------------------------------------------------
1 | import Box from '@mui/material/Box';
2 | import { alpha, styled } from '@mui/material/styles';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export const StyledLabel = styled(Box)(({ theme, ownerState }) => {
7 | const lightMode = theme.palette.mode === 'light';
8 |
9 | const filledVariant = ownerState.variant === 'filled';
10 |
11 | const outlinedVariant = ownerState.variant === 'outlined';
12 |
13 | const softVariant = ownerState.variant === 'soft';
14 |
15 | const defaultStyle = {
16 | ...(ownerState.color === 'default' && {
17 | // FILLED
18 | ...(filledVariant && {
19 | color: lightMode ? theme.palette.common.white : theme.palette.grey[800],
20 | backgroundColor: theme.palette.text.primary,
21 | }),
22 | // OUTLINED
23 | ...(outlinedVariant && {
24 | backgroundColor: 'transparent',
25 | color: theme.palette.text.primary,
26 | border: `2px solid ${theme.palette.text.primary}`,
27 | }),
28 | // SOFT
29 | ...(softVariant && {
30 | color: theme.palette.text.secondary,
31 | backgroundColor: alpha(theme.palette.grey[500], 0.16),
32 | }),
33 | }),
34 | };
35 |
36 | const colorStyle = {
37 | ...(ownerState.color !== 'default' && {
38 | // FILLED
39 | ...(filledVariant && {
40 | color: theme.palette[ownerState.color].contrastText,
41 | backgroundColor: theme.palette[ownerState.color].main,
42 | }),
43 | // OUTLINED
44 | ...(outlinedVariant && {
45 | backgroundColor: 'transparent',
46 | color: theme.palette[ownerState.color].main,
47 | border: `2px solid ${theme.palette[ownerState.color].main}`,
48 | }),
49 | // SOFT
50 | ...(softVariant && {
51 | color: theme.palette[ownerState.color][lightMode ? 'dark' : 'light'],
52 | backgroundColor: alpha(theme.palette[ownerState.color].main, 0.16),
53 | }),
54 | }),
55 | };
56 |
57 | return {
58 | height: 24,
59 | minWidth: 24,
60 | lineHeight: 0,
61 | borderRadius: 6,
62 | cursor: 'default',
63 | alignItems: 'center',
64 | whiteSpace: 'nowrap',
65 | display: 'inline-flex',
66 | justifyContent: 'center',
67 | textTransform: 'capitalize',
68 | padding: theme.spacing(0, 0.75),
69 | fontSize: theme.typography.pxToRem(12),
70 | fontWeight: theme.typography.fontWeightBold,
71 | transition: theme.transitions.create('all', {
72 | duration: theme.transitions.duration.shorter,
73 | }),
74 | ...defaultStyle,
75 | ...colorStyle,
76 | };
77 | });
78 |
--------------------------------------------------------------------------------
/admin-panel/src/components/logo/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './logo';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/components/scrollbar/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './scrollbar';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/components/scrollbar/scrollbar.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { memo, forwardRef } from 'react';
3 |
4 | import Box from '@mui/material/Box';
5 |
6 | import { StyledScrollbar, StyledRootScrollbar } from './styles';
7 |
8 | // ----------------------------------------------------------------------
9 |
10 | const Scrollbar = forwardRef(({ children, sx, ...other }, ref) => {
11 | const userAgent = typeof navigator === 'undefined' ? 'SSR' : navigator.userAgent;
12 |
13 | const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
14 |
15 | if (mobile) {
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | }
22 |
23 | return (
24 |
25 |
33 | {children}
34 |
35 |
36 | );
37 | });
38 |
39 | Scrollbar.propTypes = {
40 | children: PropTypes.node,
41 | sx: PropTypes.object,
42 | };
43 |
44 | export default memo(Scrollbar);
45 |
--------------------------------------------------------------------------------
/admin-panel/src/components/scrollbar/styles.js:
--------------------------------------------------------------------------------
1 | import SimpleBar from 'simplebar-react';
2 |
3 | import { alpha, styled } from '@mui/material/styles';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | export const StyledRootScrollbar = styled('div')(() => ({
8 | flexGrow: 1,
9 | height: '100%',
10 | overflow: 'hidden',
11 | }));
12 |
13 | export const StyledScrollbar = styled(SimpleBar)(({ theme }) => ({
14 | maxHeight: '100%',
15 | '& .simplebar-scrollbar': {
16 | '&:before': {
17 | backgroundColor: alpha(theme.palette.grey[600], 0.48),
18 | },
19 | '&.simplebar-visible:before': {
20 | opacity: 1,
21 | },
22 | },
23 | '& .simplebar-mask': {
24 | zIndex: 'inherit',
25 | },
26 | }));
27 |
--------------------------------------------------------------------------------
/admin-panel/src/components/svg-color/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './svg-color';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/components/svg-color/svg-color.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { forwardRef } from 'react';
3 |
4 | import Box from '@mui/material/Box';
5 |
6 | // ----------------------------------------------------------------------
7 |
8 | const SvgColor = forwardRef(({ src, sx, ...other }, ref) => (
9 |
24 | ));
25 |
26 | SvgColor.propTypes = {
27 | src: PropTypes.string,
28 | sx: PropTypes.object,
29 | };
30 |
31 | export default SvgColor;
32 |
--------------------------------------------------------------------------------
/admin-panel/src/global.css:
--------------------------------------------------------------------------------
1 | /* scrollbar */
2 | @import 'simplebar-react/dist/simplebar.min.css';
3 |
--------------------------------------------------------------------------------
/admin-panel/src/hooks/use-responsive.js:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/material/styles';
2 | import useMediaQuery from '@mui/material/useMediaQuery';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function useResponsive(query, start, end) {
7 | const theme = useTheme();
8 |
9 | const mediaUp = useMediaQuery(theme.breakpoints.up(start));
10 |
11 | const mediaDown = useMediaQuery(theme.breakpoints.down(start));
12 |
13 | const mediaBetween = useMediaQuery(theme.breakpoints.between(start, end));
14 |
15 | const mediaOnly = useMediaQuery(theme.breakpoints.only(start));
16 |
17 | if (query === 'up') {
18 | return mediaUp;
19 | }
20 |
21 | if (query === 'down') {
22 | return mediaDown;
23 | }
24 |
25 | if (query === 'between') {
26 | return mediaBetween;
27 | }
28 |
29 | return mediaOnly;
30 | }
31 |
32 | // ----------------------------------------------------------------------
33 |
34 | export function useWidth() {
35 | const theme = useTheme();
36 |
37 | const keys = [...theme.breakpoints.keys].reverse();
38 |
39 | return (
40 | keys.reduce((output, key) => {
41 | const matches = useMediaQuery(theme.breakpoints.up(key));
42 |
43 | return !output && matches ? key : output;
44 | }, null) || 'xs'
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/admin-panel/src/hooks/use-scroll-to-top.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function useScrollToTop() {
7 | const { pathname } = useLocation();
8 |
9 | useEffect(() => {
10 | window.scrollTo(0, 0);
11 | }, [pathname]);
12 |
13 | return null;
14 | }
15 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/common/account-popover.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | import Box from '@mui/material/Box';
4 | import Avatar from '@mui/material/Avatar';
5 | import Divider from '@mui/material/Divider';
6 | import Popover from '@mui/material/Popover';
7 | import { alpha } from '@mui/material/styles';
8 | import MenuItem from '@mui/material/MenuItem';
9 | import Typography from '@mui/material/Typography';
10 | import IconButton from '@mui/material/IconButton';
11 |
12 | import { account } from 'src/_mock/account';
13 |
14 | // ----------------------------------------------------------------------
15 |
16 | const MENU_OPTIONS = [
17 | {
18 | label: 'Home',
19 | icon: 'eva:home-fill',
20 | },
21 | {
22 | label: 'Profile',
23 | icon: 'eva:person-fill',
24 | },
25 | {
26 | label: 'Settings',
27 | icon: 'eva:settings-2-fill',
28 | },
29 | ];
30 |
31 | // ----------------------------------------------------------------------
32 |
33 | export default function AccountPopover() {
34 | const [open, setOpen] = useState(null);
35 |
36 | const handleOpen = (event) => {
37 | setOpen(event.currentTarget);
38 | };
39 |
40 | const handleClose = () => {
41 | setOpen(null);
42 | };
43 |
44 | return (
45 | <>
46 | alpha(theme.palette.grey[500], 0.08),
52 | ...(open && {
53 | background: (theme) =>
54 | `linear-gradient(135deg, ${theme.palette.primary.light} 0%, ${theme.palette.primary.main} 100%)`,
55 | }),
56 | }}
57 | >
58 | `solid 2px ${theme.palette.background.default}`,
65 | }}
66 | >
67 | {account.displayName.charAt(0).toUpperCase()}
68 |
69 |
70 |
71 |
86 |
87 |
88 | {account.displayName}
89 |
90 |
91 | {account.email}
92 |
93 |
94 |
95 |
96 |
97 | {MENU_OPTIONS.map((option) => (
98 |
99 | {option.label}
100 |
101 | ))}
102 |
103 |
104 |
105 |
111 | Logout
112 |
113 |
114 | >
115 | );
116 | }
117 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/common/language-popover.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | import Box from '@mui/material/Box';
4 | import Popover from '@mui/material/Popover';
5 | import MenuItem from '@mui/material/MenuItem';
6 | import IconButton from '@mui/material/IconButton';
7 |
8 | // ----------------------------------------------------------------------
9 |
10 | const LANGS = [
11 | {
12 | value: 'en',
13 | label: 'English',
14 | icon: '/assets/icons/ic_flag_en.svg',
15 | },
16 | {
17 | value: 'de',
18 | label: 'German',
19 | icon: '/assets/icons/ic_flag_de.svg',
20 | },
21 | {
22 | value: 'fr',
23 | label: 'French',
24 | icon: '/assets/icons/ic_flag_fr.svg',
25 | },
26 | ];
27 |
28 | // ----------------------------------------------------------------------
29 |
30 | export default function LanguagePopover() {
31 | const [open, setOpen] = useState(null);
32 |
33 | const handleOpen = (event) => {
34 | setOpen(event.currentTarget);
35 | };
36 |
37 | const handleClose = () => {
38 | setOpen(null);
39 | };
40 |
41 | return (
42 | <>
43 |
53 |
54 |
55 |
56 |
71 | {LANGS.map((option) => (
72 | handleClose()}
76 | sx={{ typography: 'body2', py: 1 }}
77 | >
78 |
79 |
80 | {option.label}
81 |
82 | ))}
83 |
84 | >
85 | );
86 | }
87 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/common/searchbar.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | import Slide from '@mui/material/Slide';
4 | import Input from '@mui/material/Input';
5 | import Button from '@mui/material/Button';
6 | import { styled } from '@mui/material/styles';
7 | import IconButton from '@mui/material/IconButton';
8 | import InputAdornment from '@mui/material/InputAdornment';
9 | import ClickAwayListener from '@mui/material/ClickAwayListener';
10 |
11 | import { bgBlur } from 'src/theme/css';
12 |
13 | import Iconify from 'src/components/iconify';
14 |
15 | // ----------------------------------------------------------------------
16 |
17 | const HEADER_MOBILE = 64;
18 | const HEADER_DESKTOP = 92;
19 |
20 | const StyledSearchbar = styled('div')(({ theme }) => ({
21 | ...bgBlur({
22 | color: theme.palette.background.default,
23 | }),
24 | top: 0,
25 | left: 0,
26 | zIndex: 99,
27 | width: '100%',
28 | display: 'flex',
29 | position: 'absolute',
30 | alignItems: 'center',
31 | height: HEADER_MOBILE,
32 | padding: theme.spacing(0, 3),
33 | boxShadow: theme.customShadows.z8,
34 | [theme.breakpoints.up('md')]: {
35 | height: HEADER_DESKTOP,
36 | padding: theme.spacing(0, 5),
37 | },
38 | }));
39 |
40 | // ----------------------------------------------------------------------
41 |
42 | export default function Searchbar() {
43 | const [open, setOpen] = useState(false);
44 |
45 | const handleOpen = () => {
46 | setOpen(!open);
47 | };
48 |
49 | const handleClose = () => {
50 | setOpen(false);
51 | };
52 |
53 | return (
54 |
55 |
56 | {!open && (
57 |
58 |
59 |
60 | )}
61 |
62 |
63 |
64 |
71 |
75 |
76 | }
77 | sx={{ mr: 1, fontWeight: 'fontWeightBold' }}
78 | />
79 |
80 | Search
81 |
82 |
83 |
84 |
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/config-layout.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export const HEADER = {
4 | H_MOBILE: 64,
5 | H_DESKTOP: 80,
6 | H_DESKTOP_OFFSET: 80 - 16,
7 | };
8 |
9 | export const NAV = {
10 | WIDTH: 280,
11 | };
12 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/config-navigation.jsx:
--------------------------------------------------------------------------------
1 | import SvgColor from 'src/components/svg-color';
2 |
3 | const icon = (name) => (
4 |
5 | );
6 |
7 | const navConfig = [
8 | {
9 | title: 'dashboard',
10 | path: '/',
11 | icon: icon('ic_analytics'),
12 | },
13 | {
14 | title: 'user',
15 | path: '/user',
16 | icon: icon('ic_user'),
17 | },
18 | ];
19 |
20 | export default navConfig;
21 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/header.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Stack from '@mui/material/Stack';
5 | import AppBar from '@mui/material/AppBar';
6 | import Toolbar from '@mui/material/Toolbar';
7 | import { useTheme } from '@mui/material/styles';
8 | import IconButton from '@mui/material/IconButton';
9 |
10 | import { useResponsive } from 'src/hooks/use-responsive';
11 |
12 | import { bgBlur } from 'src/theme/css';
13 |
14 | import Iconify from 'src/components/iconify';
15 |
16 | import Searchbar from './common/searchbar';
17 | import { NAV, HEADER } from './config-layout';
18 | import AccountPopover from './common/account-popover';
19 | import LanguagePopover from './common/language-popover';
20 | import NotificationsPopover from './common/notifications-popover';
21 |
22 | export default function Header({ onOpenNav }) {
23 | const theme = useTheme();
24 |
25 | const lgUp = useResponsive('up', 'lg');
26 |
27 | const renderContent = (
28 | <>
29 | {!lgUp && (
30 |
31 |
32 |
33 | )}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | >
45 | );
46 |
47 | return (
48 |
65 |
71 | {renderContent}
72 |
73 |
74 | );
75 | }
76 |
77 | Header.propTypes = {
78 | onOpenNav: PropTypes.func,
79 | };
80 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Box from '@mui/material/Box';
5 |
6 | import Nav from './nav';
7 | import Main from './main';
8 | import Header from './header';
9 |
10 | // ----------------------------------------------------------------------
11 |
12 | export default function DashboardLayout({ children }) {
13 | const [openNav, setOpenNav] = useState(false);
14 |
15 | return (
16 | <>
17 | setOpenNav(true)} />
18 |
19 |
26 | setOpenNav(false)} />
27 |
28 | {children}
29 |
30 | >
31 | );
32 | }
33 |
34 | DashboardLayout.propTypes = {
35 | children: PropTypes.node,
36 | };
37 |
--------------------------------------------------------------------------------
/admin-panel/src/layouts/dashboard/main.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 |
5 | import { useResponsive } from 'src/hooks/use-responsive';
6 |
7 | import { NAV, HEADER } from './config-layout';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | const SPACING = 8;
12 |
13 | export default function Main({ children, sx, ...other }) {
14 | const lgUp = useResponsive('up', 'lg');
15 |
16 | return (
17 |
34 | {children}
35 |
36 | );
37 | }
38 |
39 | Main.propTypes = {
40 | children: PropTypes.node,
41 | sx: PropTypes.object,
42 | };
43 |
--------------------------------------------------------------------------------
/admin-panel/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { Suspense } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import { HelmetProvider } from 'react-helmet-async';
5 |
6 | import App from './app';
7 |
8 | const root = ReactDOM.createRoot(document.getElementById('root'));
9 |
10 | root.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/admin-panel/src/pages/app.jsx:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet-async';
2 |
3 | import { AppView } from 'src/sections/overview/view';
4 |
5 | export default function AppPage() {
6 | return (
7 | <>
8 |
9 | Dashboard | QuickNotes
10 |
11 |
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/admin-panel/src/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet-async';
2 |
3 | import { LoginView } from 'src/sections/login';
4 |
5 | export default function LoginPage() {
6 | return (
7 | <>
8 |
9 | Login | QuickNotes
10 |
11 |
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/admin-panel/src/pages/user.jsx:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet-async';
2 |
3 | import { UserView } from 'src/sections/user/view';
4 |
5 | export default function UserPage() {
6 | return (
7 | <>
8 |
9 | User | QuickNotes
10 |
11 |
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/admin-panel/src/providers/AuthProvider.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { useState, useContext, createContext } from "react";
3 |
4 | const AuthContext = createContext();
5 |
6 | export default function AuthProvider({ children }) {
7 | const [isLoggedIn, setIsLoggedIn] = useState(false);
8 |
9 | const login = async (email, password) => {
10 | try {
11 | const response = await fetch(`${import.meta.env.VITE_API_URL}/login`, {
12 | method: 'POST',
13 | headers: {
14 | 'Content-Type': 'application/json'
15 | },
16 | credentials: 'include',
17 | body: JSON.stringify({ email, password }),
18 | });
19 |
20 | if (!response.ok) {
21 | console.error('Login failed:', response.data.message);
22 | } else {
23 | setIsLoggedIn(true);
24 | }
25 | } catch (error) {
26 | console.error('Login error:', error);
27 | }
28 | };
29 |
30 | const logout = () => setIsLoggedIn(false);
31 |
32 | return {children} ;
33 | }
34 |
35 | export const useAuth = () => useContext(AuthContext);
36 |
37 | AuthProvider.propTypes = {
38 | children: PropTypes.any,
39 | };
--------------------------------------------------------------------------------
/admin-panel/src/routes/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as RouterLink } from './router-link';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/routes/components/router-link.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { forwardRef } from 'react';
3 | import { Link } from 'react-router-dom';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | const RouterLink = forwardRef(({ href, ...other }, ref) => );
8 |
9 | RouterLink.propTypes = {
10 | href: PropTypes.string,
11 | };
12 |
13 | export default RouterLink;
14 |
--------------------------------------------------------------------------------
/admin-panel/src/routes/hooks/index.js:
--------------------------------------------------------------------------------
1 | export { useRouter } from './use-router';
2 | export { usePathname } from './use-pathname';
3 |
--------------------------------------------------------------------------------
/admin-panel/src/routes/hooks/use-pathname.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function usePathname() {
7 | const { pathname } = useLocation();
8 |
9 | return useMemo(() => pathname, [pathname]);
10 | }
11 |
--------------------------------------------------------------------------------
/admin-panel/src/routes/hooks/use-router.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 |
4 | // ----------------------------------------------------------------------
5 |
6 | export function useRouter() {
7 | const navigate = useNavigate();
8 |
9 | const router = useMemo(
10 | () => ({
11 | back: () => navigate(-1),
12 | forward: () => navigate(1),
13 | reload: () => window.location.reload(),
14 | push: (href) => navigate(href),
15 | replace: (href) => navigate(href, { replace: true }),
16 | }),
17 | [navigate]
18 | );
19 |
20 | return router;
21 | }
22 |
--------------------------------------------------------------------------------
/admin-panel/src/routes/sections.jsx:
--------------------------------------------------------------------------------
1 | import { lazy, Suspense } from 'react';
2 | import { Outlet, Navigate, useRoutes } from 'react-router-dom';
3 |
4 | import DashboardLayout from 'src/layouts/dashboard';
5 | import { useAuth } from 'src/providers/AuthProvider';
6 |
7 | export const IndexPage = lazy(() => import('src/pages/app'));
8 | export const UserPage = lazy(() => import('src/pages/user'));
9 | export const LoginPage = lazy(() => import('src/pages/login'));
10 |
11 | export default function Router() {
12 | const { isLoggedIn } = useAuth();
13 | const requireAuth = (element) => isLoggedIn ? element : ;
14 |
15 | const routes = useRoutes([
16 | {
17 | element: (
18 |
19 | Loading...}>
20 |
21 |
22 |
23 | ),
24 | children: [
25 | { element: requireAuth( ), index: true },
26 | { path: 'user', element: requireAuth( ) },
27 | ],
28 | },
29 | {
30 | path: 'login',
31 | element: isLoggedIn ? : ,
32 | }
33 | ])
34 |
35 | return routes;
36 | }
37 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/login/index.js:
--------------------------------------------------------------------------------
1 | export { default as LoginView } from './login-view';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-conversion-rates.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Card from '@mui/material/Card';
5 | import CardHeader from '@mui/material/CardHeader';
6 |
7 | import { fNumber } from 'src/utils/format-number';
8 |
9 | import Chart, { useChart } from 'src/components/chart';
10 |
11 | // ----------------------------------------------------------------------
12 |
13 | export default function AppConversionRates({ title, subheader, chart, ...other }) {
14 | const { colors, series, options } = chart;
15 |
16 | const chartSeries = series.map((i) => i.value);
17 |
18 | const chartOptions = useChart({
19 | colors,
20 | tooltip: {
21 | marker: { show: false },
22 | y: {
23 | formatter: (value) => fNumber(value),
24 | title: {
25 | formatter: () => '',
26 | },
27 | },
28 | },
29 | plotOptions: {
30 | bar: {
31 | horizontal: true,
32 | barHeight: '28%',
33 | borderRadius: 2,
34 | },
35 | },
36 | xaxis: {
37 | categories: series.map((i) => i.label),
38 | },
39 | ...options,
40 | });
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
55 |
56 |
57 | );
58 | }
59 |
60 | AppConversionRates.propTypes = {
61 | chart: PropTypes.object,
62 | subheader: PropTypes.string,
63 | title: PropTypes.string,
64 | };
65 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-current-subject.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Card from '@mui/material/Card';
4 | import CardHeader from '@mui/material/CardHeader';
5 | import { styled, useTheme } from '@mui/material/styles';
6 |
7 | import Chart, { useChart } from 'src/components/chart';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | const CHART_HEIGHT = 400;
12 |
13 | const LEGEND_HEIGHT = 72;
14 |
15 | const StyledChart = styled(Chart)(({ theme }) => ({
16 | height: CHART_HEIGHT,
17 | '& .apexcharts-canvas, .apexcharts-inner, svg, foreignObject': {
18 | height: `100% !important`,
19 | },
20 | '& .apexcharts-legend': {
21 | height: LEGEND_HEIGHT,
22 | borderTop: `dashed 1px ${theme.palette.divider}`,
23 | top: `calc(${CHART_HEIGHT - LEGEND_HEIGHT}px) !important`,
24 | },
25 | }));
26 |
27 | // ----------------------------------------------------------------------
28 |
29 | export default function AppCurrentSubject({ title, subheader, chart, ...other }) {
30 | const theme = useTheme();
31 |
32 | const { series, colors, categories, options } = chart;
33 |
34 | const chartOptions = useChart({
35 | colors,
36 | stroke: {
37 | width: 2,
38 | },
39 | fill: {
40 | opacity: 0.48,
41 | },
42 | legend: {
43 | floating: true,
44 | position: 'bottom',
45 | horizontalAlign: 'center',
46 | },
47 | xaxis: {
48 | categories,
49 | labels: {
50 | style: {
51 | colors: [...Array(6)].map(() => theme.palette.text.secondary),
52 | },
53 | },
54 | },
55 | ...options,
56 | });
57 |
58 | return (
59 |
60 |
61 |
62 |
70 |
71 | );
72 | }
73 |
74 | AppCurrentSubject.propTypes = {
75 | chart: PropTypes.object,
76 | subheader: PropTypes.string,
77 | title: PropTypes.string,
78 | };
79 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-current-visits.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Card from '@mui/material/Card';
4 | import CardHeader from '@mui/material/CardHeader';
5 | import { styled, useTheme } from '@mui/material/styles';
6 |
7 | import { fNumber } from 'src/utils/format-number';
8 |
9 | import Chart, { useChart } from 'src/components/chart';
10 |
11 | // ----------------------------------------------------------------------
12 |
13 | const CHART_HEIGHT = 400;
14 |
15 | const LEGEND_HEIGHT = 72;
16 |
17 | const StyledChart = styled(Chart)(({ theme }) => ({
18 | height: CHART_HEIGHT,
19 | '& .apexcharts-canvas, .apexcharts-inner, svg, foreignObject': {
20 | height: `100% !important`,
21 | },
22 | '& .apexcharts-legend': {
23 | height: LEGEND_HEIGHT,
24 | borderTop: `dashed 1px ${theme.palette.divider}`,
25 | top: `calc(${CHART_HEIGHT - LEGEND_HEIGHT}px) !important`,
26 | },
27 | }));
28 |
29 | // ----------------------------------------------------------------------
30 |
31 | export default function AppCurrentVisits({ title, subheader, chart, ...other }) {
32 | const theme = useTheme();
33 |
34 | const { colors, series, options } = chart;
35 |
36 | const chartSeries = series.map((i) => i.value);
37 |
38 | const chartOptions = useChart({
39 | chart: {
40 | sparkline: {
41 | enabled: true,
42 | },
43 | },
44 | colors,
45 | labels: series.map((i) => i.label),
46 | stroke: {
47 | colors: [theme.palette.background.paper],
48 | },
49 | legend: {
50 | floating: true,
51 | position: 'bottom',
52 | horizontalAlign: 'center',
53 | },
54 | dataLabels: {
55 | enabled: true,
56 | dropShadow: {
57 | enabled: false,
58 | },
59 | },
60 | tooltip: {
61 | fillSeriesColor: false,
62 | y: {
63 | formatter: (value) => fNumber(value),
64 | title: {
65 | formatter: (seriesName) => `${seriesName}`,
66 | },
67 | },
68 | },
69 | plotOptions: {
70 | pie: {
71 | donut: {
72 | labels: {
73 | show: false,
74 | },
75 | },
76 | },
77 | },
78 | ...options,
79 | });
80 |
81 | return (
82 |
83 |
84 |
85 |
93 |
94 | );
95 | }
96 |
97 | AppCurrentVisits.propTypes = {
98 | chart: PropTypes.object,
99 | subheader: PropTypes.string,
100 | title: PropTypes.string,
101 | };
102 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-news-update.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Link from '@mui/material/Link';
5 | import Card from '@mui/material/Card';
6 | import Stack from '@mui/material/Stack';
7 | import Button from '@mui/material/Button';
8 | import Divider from '@mui/material/Divider';
9 | import Typography from '@mui/material/Typography';
10 | import CardHeader from '@mui/material/CardHeader';
11 |
12 | import { fToNow } from 'src/utils/format-time';
13 |
14 | import Iconify from 'src/components/iconify';
15 | import Scrollbar from 'src/components/scrollbar';
16 |
17 | // ----------------------------------------------------------------------
18 |
19 | export default function AppNewsUpdate({ title, subheader, list, ...other }) {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | {list.map((news) => (
27 |
28 | ))}
29 |
30 |
31 |
32 |
33 |
34 |
35 | }
39 | >
40 | View all
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | AppNewsUpdate.propTypes = {
48 | title: PropTypes.string,
49 | subheader: PropTypes.string,
50 | list: PropTypes.array.isRequired,
51 | };
52 |
53 | // ----------------------------------------------------------------------
54 |
55 | function NewsItem({ news }) {
56 | const { image, title, description, postedAt } = news;
57 |
58 | return (
59 |
60 |
66 |
67 |
68 |
69 | {title}
70 |
71 |
72 |
73 | {description}
74 |
75 |
76 |
77 |
78 | {fToNow(postedAt)}
79 |
80 |
81 | );
82 | }
83 |
84 | NewsItem.propTypes = {
85 | news: PropTypes.shape({
86 | image: PropTypes.string,
87 | title: PropTypes.string,
88 | description: PropTypes.string,
89 | postedAt: PropTypes.instanceOf(Date),
90 | }),
91 | };
92 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-order-timeline.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Card from '@mui/material/Card';
4 | import Timeline from '@mui/lab/Timeline';
5 | import TimelineDot from '@mui/lab/TimelineDot';
6 | import Typography from '@mui/material/Typography';
7 | import CardHeader from '@mui/material/CardHeader';
8 | import TimelineContent from '@mui/lab/TimelineContent';
9 | import TimelineSeparator from '@mui/lab/TimelineSeparator';
10 | import TimelineConnector from '@mui/lab/TimelineConnector';
11 | import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
12 |
13 | import { fDateTime } from 'src/utils/format-time';
14 |
15 | // ----------------------------------------------------------------------
16 |
17 | export default function AnalyticsOrderTimeline({ title, subheader, list, ...other }) {
18 | return (
19 |
20 |
21 |
22 |
32 | {list.map((item, index) => (
33 |
34 | ))}
35 |
36 |
37 | );
38 | }
39 |
40 | AnalyticsOrderTimeline.propTypes = {
41 | list: PropTypes.array,
42 | subheader: PropTypes.string,
43 | title: PropTypes.string,
44 | };
45 |
46 | // ----------------------------------------------------------------------
47 |
48 | function OrderItem({ item, lastTimeline }) {
49 | const { type, title, time } = item;
50 | return (
51 |
52 |
53 |
62 | {lastTimeline ? null : }
63 |
64 |
65 |
66 | {title}
67 |
68 |
69 | {fDateTime(time)}
70 |
71 |
72 |
73 | );
74 | }
75 |
76 | OrderItem.propTypes = {
77 | item: PropTypes.object,
78 | lastTimeline: PropTypes.bool,
79 | };
80 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-traffic-by-site.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Card from '@mui/material/Card';
5 | import Paper from '@mui/material/Paper';
6 | import Typography from '@mui/material/Typography';
7 | import CardHeader from '@mui/material/CardHeader';
8 |
9 | import { fShortenNumber } from 'src/utils/format-number';
10 |
11 | // ----------------------------------------------------------------------
12 |
13 | export default function AppTrafficBySite({ title, subheader, list, ...other }) {
14 | return (
15 |
16 |
17 |
18 |
26 | {list.map((site) => (
27 |
32 | {site.icon}
33 |
34 | {fShortenNumber(site.value)}
35 |
36 |
37 | {site.name}
38 |
39 |
40 | ))}
41 |
42 |
43 | );
44 | }
45 |
46 | AppTrafficBySite.propTypes = {
47 | title: PropTypes.string,
48 | subheader: PropTypes.string,
49 | list: PropTypes.array.isRequired,
50 | };
51 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-website-visits.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Card from '@mui/material/Card';
5 | import CardHeader from '@mui/material/CardHeader';
6 |
7 | import Chart, { useChart } from 'src/components/chart';
8 |
9 | // ----------------------------------------------------------------------
10 |
11 | export default function AppWebsiteVisits({ title, subheader, chart, ...other }) {
12 | const { labels, colors, series, options } = chart;
13 |
14 | const chartOptions = useChart({
15 | colors,
16 | plotOptions: {
17 | bar: {
18 | columnWidth: '16%',
19 | },
20 | },
21 | fill: {
22 | type: series.map((i) => i.fill),
23 | },
24 | labels,
25 | xaxis: {
26 | type: 'datetime',
27 | },
28 | tooltip: {
29 | shared: true,
30 | intersect: false,
31 | y: {
32 | formatter: (value) => {
33 | if (typeof value !== 'undefined') {
34 | return `${value.toFixed(0)} visits`;
35 | }
36 | return value;
37 | },
38 | },
39 | },
40 | ...options,
41 | });
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
56 |
57 |
58 | );
59 | }
60 |
61 | AppWebsiteVisits.propTypes = {
62 | chart: PropTypes.object,
63 | subheader: PropTypes.string,
64 | title: PropTypes.string,
65 | };
66 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/app-widget-summary.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import Card from '@mui/material/Card';
5 | import Stack from '@mui/material/Stack';
6 | import Typography from '@mui/material/Typography';
7 |
8 | import { fShortenNumber } from 'src/utils/format-number';
9 |
10 | // ----------------------------------------------------------------------
11 |
12 | export default function AppWidgetSummary({ title, total, icon, color = 'primary', sx, ...other }) {
13 | return (
14 |
26 | {icon && {icon} }
27 |
28 |
29 | {fShortenNumber(total)}
30 |
31 |
32 | {title}
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | AppWidgetSummary.propTypes = {
40 | color: PropTypes.string,
41 | icon: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
42 | sx: PropTypes.object,
43 | title: PropTypes.string,
44 | total: PropTypes.number,
45 | };
46 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/overview/view/index.js:
--------------------------------------------------------------------------------
1 | export { default as AppView } from './app-view';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/table-empty-rows.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import TableRow from '@mui/material/TableRow';
4 | import TableCell from '@mui/material/TableCell';
5 |
6 | // ----------------------------------------------------------------------
7 |
8 | export default function TableEmptyRows({ emptyRows, height }) {
9 | if (!emptyRows) {
10 | return null;
11 | }
12 |
13 | return (
14 |
21 |
22 |
23 | );
24 | }
25 |
26 | TableEmptyRows.propTypes = {
27 | emptyRows: PropTypes.number,
28 | height: PropTypes.number,
29 | };
30 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/table-no-data.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Paper from '@mui/material/Paper';
4 | import TableRow from '@mui/material/TableRow';
5 | import TableCell from '@mui/material/TableCell';
6 | import Typography from '@mui/material/Typography';
7 |
8 | // ----------------------------------------------------------------------
9 |
10 | export default function TableNoData({ query }) {
11 | return (
12 |
13 |
14 |
19 |
20 | Not found
21 |
22 |
23 |
24 | No results found for
25 | "{query}" .
26 | Try checking for typos or using complete words.
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | TableNoData.propTypes = {
35 | query: PropTypes.string,
36 | };
37 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/user-table-head.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Box from '@mui/material/Box';
4 | import TableRow from '@mui/material/TableRow';
5 | import Checkbox from '@mui/material/Checkbox';
6 | import TableHead from '@mui/material/TableHead';
7 | import TableCell from '@mui/material/TableCell';
8 | import TableSortLabel from '@mui/material/TableSortLabel';
9 |
10 | import { visuallyHidden } from './utils';
11 |
12 | // ----------------------------------------------------------------------
13 |
14 | export default function UserTableHead({
15 | order,
16 | orderBy,
17 | rowCount,
18 | headLabel,
19 | numSelected,
20 | onRequestSort,
21 | onSelectAllClick,
22 | }) {
23 | const onSort = (property) => (event) => {
24 | onRequestSort(event, property);
25 | };
26 |
27 | return (
28 |
29 |
30 |
31 | 0 && numSelected < rowCount}
33 | checked={rowCount > 0 && numSelected === rowCount}
34 | onChange={onSelectAllClick}
35 | />
36 |
37 |
38 | {headLabel.map((headCell) => (
39 |
45 |
51 | {headCell.label}
52 | {orderBy === headCell.id ? (
53 |
54 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'}
55 |
56 | ) : null}
57 |
58 |
59 | ))}
60 |
61 |
62 | );
63 | }
64 |
65 | UserTableHead.propTypes = {
66 | order: PropTypes.oneOf(['asc', 'desc']),
67 | orderBy: PropTypes.string,
68 | rowCount: PropTypes.number,
69 | headLabel: PropTypes.array,
70 | numSelected: PropTypes.number,
71 | onRequestSort: PropTypes.func,
72 | onSelectAllClick: PropTypes.func,
73 | };
74 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/user-table-row.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Stack from '@mui/material/Stack';
5 | import Avatar from '@mui/material/Avatar';
6 | import Popover from '@mui/material/Popover';
7 | import TableRow from '@mui/material/TableRow';
8 | import Checkbox from '@mui/material/Checkbox';
9 | import MenuItem from '@mui/material/MenuItem';
10 | import TableCell from '@mui/material/TableCell';
11 | import Typography from '@mui/material/Typography';
12 | import IconButton from '@mui/material/IconButton';
13 |
14 | import Label from 'src/components/label';
15 | import Iconify from 'src/components/iconify';
16 |
17 | // ----------------------------------------------------------------------
18 |
19 | export default function UserTableRow({
20 | selected,
21 | name,
22 | avatarUrl,
23 | email,
24 | role,
25 | isVerified,
26 | status,
27 | handleClick,
28 | }) {
29 | const [open, setOpen] = useState(null);
30 |
31 | const handleOpenMenu = (event) => {
32 | setOpen(event.currentTarget);
33 | };
34 |
35 | const handleCloseMenu = () => {
36 | setOpen(null);
37 | };
38 |
39 | return (
40 | <>
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {name}
51 |
52 |
53 |
54 |
55 | {email}
56 |
57 | {role}
58 |
59 | {isVerified ? 'Yes' : 'No'}
60 |
61 |
62 | {status}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
82 |
83 |
84 | Edit
85 |
86 |
87 |
88 |
89 | Delete
90 |
91 |
92 | >
93 | );
94 | }
95 |
96 | UserTableRow.propTypes = {
97 | avatarUrl: PropTypes.any,
98 | email: PropTypes.any,
99 | handleClick: PropTypes.func,
100 | isVerified: PropTypes.any,
101 | name: PropTypes.any,
102 | role: PropTypes.any,
103 | selected: PropTypes.any,
104 | status: PropTypes.string,
105 | };
106 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/user-table-toolbar.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import Tooltip from '@mui/material/Tooltip';
4 | import Toolbar from '@mui/material/Toolbar';
5 | import Typography from '@mui/material/Typography';
6 | import IconButton from '@mui/material/IconButton';
7 | import OutlinedInput from '@mui/material/OutlinedInput';
8 | import InputAdornment from '@mui/material/InputAdornment';
9 |
10 | import Iconify from 'src/components/iconify';
11 |
12 | // ----------------------------------------------------------------------
13 |
14 | export default function UserTableToolbar({ numSelected, filterName, onFilterName }) {
15 | return (
16 | theme.spacing(0, 1, 0, 3),
22 | ...(numSelected > 0 && {
23 | color: 'primary.main',
24 | bgcolor: 'primary.lighter',
25 | }),
26 | }}
27 | >
28 | {numSelected > 0 ? (
29 |
30 | {numSelected} selected
31 |
32 | ) : (
33 |
39 |
43 |
44 | }
45 | />
46 | )}
47 |
48 | {numSelected > 0 ? (
49 |
50 |
51 |
52 |
53 |
54 | ) : (
55 |
56 |
57 |
58 |
59 |
60 | )}
61 |
62 | );
63 | }
64 |
65 | UserTableToolbar.propTypes = {
66 | numSelected: PropTypes.number,
67 | filterName: PropTypes.string,
68 | onFilterName: PropTypes.func,
69 | };
70 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/utils.js:
--------------------------------------------------------------------------------
1 | export const visuallyHidden = {
2 | border: 0,
3 | margin: -1,
4 | padding: 0,
5 | width: '1px',
6 | height: '1px',
7 | overflow: 'hidden',
8 | position: 'absolute',
9 | whiteSpace: 'nowrap',
10 | clip: 'rect(0 0 0 0)',
11 | };
12 |
13 | export function emptyRows(page, rowsPerPage, arrayLength) {
14 | return page ? Math.max(0, (1 + page) * rowsPerPage - arrayLength) : 0;
15 | }
16 |
17 | function descendingComparator(a, b, orderBy) {
18 | if (a[orderBy] === null) {
19 | return 1;
20 | }
21 | if (b[orderBy] === null) {
22 | return -1;
23 | }
24 | if (b[orderBy] < a[orderBy]) {
25 | return -1;
26 | }
27 | if (b[orderBy] > a[orderBy]) {
28 | return 1;
29 | }
30 | return 0;
31 | }
32 | export function getComparator(order, orderBy) {
33 | return order === 'desc'
34 | ? (a, b) => descendingComparator(a, b, orderBy)
35 | : (a, b) => -descendingComparator(a, b, orderBy);
36 | }
37 |
38 | export function applyFilter({ inputData, comparator, filterName }) {
39 | const stabilizedThis = inputData.map((el, index) => [el, index]);
40 |
41 | stabilizedThis.sort((a, b) => {
42 | const order = comparator(a[0], b[0]);
43 | if (order !== 0) return order;
44 | return a[1] - b[1];
45 | });
46 |
47 | inputData = stabilizedThis.map((el) => el[0]);
48 |
49 | if (filterName) {
50 | inputData = inputData.filter(
51 | (user) => user.name.toLowerCase().indexOf(filterName.toLowerCase()) !== -1
52 | );
53 | }
54 |
55 | return inputData;
56 | }
57 |
--------------------------------------------------------------------------------
/admin-panel/src/sections/user/view/index.js:
--------------------------------------------------------------------------------
1 | export { default as UserView } from './user-view';
2 |
--------------------------------------------------------------------------------
/admin-panel/src/theme/custom-shadows.js:
--------------------------------------------------------------------------------
1 | import { alpha } from '@mui/material/styles';
2 |
3 | import { grey, info, error, common, primary, success, warning, secondary } from './palette';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | export function customShadows() {
8 | const transparent = alpha(grey[500], 0.16);
9 |
10 | return {
11 | z1: `0 1px 2px 0 ${transparent}`,
12 | z4: `0 4px 8px 0 ${transparent}`,
13 | z8: `0 8px 16px 0 ${transparent}`,
14 | z12: `0 12px 24px -4px ${transparent}`,
15 | z16: `0 16px 32px -4px ${transparent}`,
16 | z20: `0 20px 40px -4px ${transparent}`,
17 | z24: `0 24px 48px 0 ${transparent}`,
18 | //
19 | card: `0 0 2px 0 ${alpha(grey[500], 0.08)}, 0 12px 24px -4px ${alpha(grey[500], 0.08)}`,
20 | dropdown: `0 0 2px 0 ${alpha(grey[500], 0.24)}, -20px 20px 40px -4px ${alpha(grey[500], 0.24)}`,
21 | dialog: `-40px 40px 80px -8px ${alpha(common.black, 0.24)}`,
22 | //
23 | primary: `0 8px 16px 0 ${alpha(primary.main, 0.24)}`,
24 | info: `0 8px 16px 0 ${alpha(info.main, 0.24)}`,
25 | secondary: `0 8px 16px 0 ${alpha(secondary.main, 0.24)}`,
26 | success: `0 8px 16px 0 ${alpha(success.main, 0.24)}`,
27 | warning: `0 8px 16px 0 ${alpha(warning.main, 0.24)}`,
28 | error: `0 8px 16px 0 ${alpha(error.main, 0.24)}`,
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/admin-panel/src/theme/index.jsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import CssBaseline from '@mui/material/CssBaseline';
5 | import { createTheme, ThemeProvider as MUIThemeProvider } from '@mui/material/styles';
6 |
7 | import { palette } from './palette';
8 | import { shadows } from './shadows';
9 | import { overrides } from './overrides';
10 | import { typography } from './typography';
11 | import { customShadows } from './custom-shadows';
12 |
13 | // ----------------------------------------------------------------------
14 |
15 | export default function ThemeProvider({ children }) {
16 | const memoizedValue = useMemo(
17 | () => ({
18 | palette: palette(),
19 | typography,
20 | shadows: shadows(),
21 | customShadows: customShadows(),
22 | shape: { borderRadius: 8 },
23 | }),
24 | []
25 | );
26 |
27 | const theme = createTheme(memoizedValue);
28 |
29 | theme.components = overrides(theme);
30 |
31 | return (
32 |
33 |
34 | {children}
35 |
36 | );
37 | }
38 |
39 | ThemeProvider.propTypes = {
40 | children: PropTypes.node,
41 | };
42 |
--------------------------------------------------------------------------------
/admin-panel/src/theme/palette.js:
--------------------------------------------------------------------------------
1 | import { alpha } from '@mui/material/styles';
2 |
3 | // ----------------------------------------------------------------------
4 |
5 | // SETUP COLORS
6 |
7 | export const grey = {
8 | 0: '#FFFFFF',
9 | 100: '#F9FAFB',
10 | 200: '#F4F6F8',
11 | 300: '#DFE3E8',
12 | 400: '#C4CDD5',
13 | 500: '#919EAB',
14 | 600: '#637381',
15 | 700: '#454F5B',
16 | 800: '#212B36',
17 | 900: '#161C24',
18 | };
19 |
20 | export const primary = {
21 | lighter: '#D0ECFE',
22 | light: '#73BAFB',
23 | main: '#1877F2',
24 | dark: '#0C44AE',
25 | darker: '#042174',
26 | contrastText: '#FFFFFF',
27 | };
28 |
29 | export const secondary = {
30 | lighter: '#EFD6FF',
31 | light: '#C684FF',
32 | main: '#8E33FF',
33 | dark: '#5119B7',
34 | darker: '#27097A',
35 | contrastText: '#FFFFFF',
36 | };
37 |
38 | export const info = {
39 | lighter: '#CAFDF5',
40 | light: '#61F3F3',
41 | main: '#00B8D9',
42 | dark: '#006C9C',
43 | darker: '#003768',
44 | contrastText: '#FFFFFF',
45 | };
46 |
47 | export const success = {
48 | lighter: '#C8FAD6',
49 | light: '#5BE49B',
50 | main: '#00A76F',
51 | dark: '#007867',
52 | darker: '#004B50',
53 | contrastText: '#FFFFFF',
54 | };
55 |
56 | export const warning = {
57 | lighter: '#FFF5CC',
58 | light: '#FFD666',
59 | main: '#FFAB00',
60 | dark: '#B76E00',
61 | darker: '#7A4100',
62 | contrastText: grey[800],
63 | };
64 |
65 | export const error = {
66 | lighter: '#FFE9D5',
67 | light: '#FFAC82',
68 | main: '#FF5630',
69 | dark: '#B71D18',
70 | darker: '#7A0916',
71 | contrastText: '#FFFFFF',
72 | };
73 |
74 | export const common = {
75 | black: '#000000',
76 | white: '#FFFFFF',
77 | };
78 |
79 | export const action = {
80 | hover: alpha(grey[500], 0.08),
81 | selected: alpha(grey[500], 0.16),
82 | disabled: alpha(grey[500], 0.8),
83 | disabledBackground: alpha(grey[500], 0.24),
84 | focus: alpha(grey[500], 0.24),
85 | hoverOpacity: 0.08,
86 | disabledOpacity: 0.48,
87 | };
88 |
89 | const base = {
90 | primary,
91 | secondary,
92 | info,
93 | success,
94 | warning,
95 | error,
96 | grey,
97 | common,
98 | divider: alpha(grey[500], 0.2),
99 | action,
100 | };
101 |
102 | // ----------------------------------------------------------------------
103 |
104 | export function palette() {
105 | return {
106 | ...base,
107 | mode: 'light',
108 | text: {
109 | primary: grey[800],
110 | secondary: grey[600],
111 | disabled: grey[500],
112 | },
113 | background: {
114 | paper: '#FFFFFF',
115 | default: grey[100],
116 | neutral: grey[200],
117 | },
118 | action: {
119 | ...base.action,
120 | active: grey[600],
121 | },
122 | };
123 | }
124 |
--------------------------------------------------------------------------------
/admin-panel/src/theme/shadows.js:
--------------------------------------------------------------------------------
1 | import { alpha } from '@mui/material/styles';
2 |
3 | import { grey } from './palette';
4 |
5 | // ----------------------------------------------------------------------
6 |
7 | export function shadows() {
8 | const transparent1 = alpha(grey[500], 0.2);
9 | const transparent2 = alpha(grey[500], 0.14);
10 | const transparent3 = alpha(grey[500], 0.12);
11 |
12 | return [
13 | 'none',
14 | `0px 2px 1px -1px ${transparent1},0px 1px 1px 0px ${transparent2},0px 1px 3px 0px ${transparent3}`,
15 | `0px 3px 1px -2px ${transparent1},0px 2px 2px 0px ${transparent2},0px 1px 5px 0px ${transparent3}`,
16 | `0px 3px 3px -2px ${transparent1},0px 3px 4px 0px ${transparent2},0px 1px 8px 0px ${transparent3}`,
17 | `0px 2px 4px -1px ${transparent1},0px 4px 5px 0px ${transparent2},0px 1px 10px 0px ${transparent3}`,
18 | `0px 3px 5px -1px ${transparent1},0px 5px 8px 0px ${transparent2},0px 1px 14px 0px ${transparent3}`,
19 | `0px 3px 5px -1px ${transparent1},0px 6px 10px 0px ${transparent2},0px 1px 18px 0px ${transparent3}`,
20 | `0px 4px 5px -2px ${transparent1},0px 7px 10px 1px ${transparent2},0px 2px 16px 1px ${transparent3}`,
21 | `0px 5px 5px -3px ${transparent1},0px 8px 10px 1px ${transparent2},0px 3px 14px 2px ${transparent3}`,
22 | `0px 5px 6px -3px ${transparent1},0px 9px 12px 1px ${transparent2},0px 3px 16px 2px ${transparent3}`,
23 | `0px 6px 6px -3px ${transparent1},0px 10px 14px 1px ${transparent2},0px 4px 18px 3px ${transparent3}`,
24 | `0px 6px 7px -4px ${transparent1},0px 11px 15px 1px ${transparent2},0px 4px 20px 3px ${transparent3}`,
25 | `0px 7px 8px -4px ${transparent1},0px 12px 17px 2px ${transparent2},0px 5px 22px 4px ${transparent3}`,
26 | `0px 7px 8px -4px ${transparent1},0px 13px 19px 2px ${transparent2},0px 5px 24px 4px ${transparent3}`,
27 | `0px 7px 9px -4px ${transparent1},0px 14px 21px 2px ${transparent2},0px 5px 26px 4px ${transparent3}`,
28 | `0px 8px 9px -5px ${transparent1},0px 15px 22px 2px ${transparent2},0px 6px 28px 5px ${transparent3}`,
29 | `0px 8px 10px -5px ${transparent1},0px 16px 24px 2px ${transparent2},0px 6px 30px 5px ${transparent3}`,
30 | `0px 8px 11px -5px ${transparent1},0px 17px 26px 2px ${transparent2},0px 6px 32px 5px ${transparent3}`,
31 | `0px 9px 11px -5px ${transparent1},0px 18px 28px 2px ${transparent2},0px 7px 34px 6px ${transparent3}`,
32 | `0px 9px 12px -6px ${transparent1},0px 19px 29px 2px ${transparent2},0px 7px 36px 6px ${transparent3}`,
33 | `0px 10px 13px -6px ${transparent1},0px 20px 31px 3px ${transparent2},0px 8px 38px 7px ${transparent3}`,
34 | `0px 10px 13px -6px ${transparent1},0px 21px 33px 3px ${transparent2},0px 8px 40px 7px ${transparent3}`,
35 | `0px 10px 14px -6px ${transparent1},0px 22px 35px 3px ${transparent2},0px 8px 42px 7px ${transparent3}`,
36 | `0px 11px 14px -7px ${transparent1},0px 23px 36px 3px ${transparent2},0px 9px 44px 8px ${transparent3}`,
37 | `0px 11px 15px -7px ${transparent1},0px 24px 38px 3px ${transparent2},0px 9px 46px 8px ${transparent3}`,
38 | ];
39 | }
40 |
--------------------------------------------------------------------------------
/admin-panel/src/theme/typography.js:
--------------------------------------------------------------------------------
1 | // ----------------------------------------------------------------------
2 |
3 | export function remToPx(value) {
4 | return Math.round(parseFloat(value) * 16);
5 | }
6 |
7 | export function pxToRem(value) {
8 | return `${value / 16}rem`;
9 | }
10 |
11 | export function responsiveFontSizes({ sm, md, lg }) {
12 | return {
13 | '@media (min-width:600px)': {
14 | fontSize: pxToRem(sm),
15 | },
16 | '@media (min-width:900px)': {
17 | fontSize: pxToRem(md),
18 | },
19 | '@media (min-width:1200px)': {
20 | fontSize: pxToRem(lg),
21 | },
22 | };
23 | }
24 |
25 | export const primaryFont = 'Public Sans, sans-serif';
26 | export const secondaryFont = 'Barlow, sans-serif';
27 |
28 | // ----------------------------------------------------------------------
29 |
30 | export const typography = {
31 | fontFamily: primaryFont,
32 | fontSecondaryFamily: secondaryFont,
33 | fontWeightRegular: 400,
34 | fontWeightMedium: 500,
35 | fontWeightSemiBold: 600,
36 | fontWeightBold: 700,
37 | h1: {
38 | fontWeight: 800,
39 | lineHeight: 80 / 64,
40 | fontSize: pxToRem(40),
41 | ...responsiveFontSizes({ sm: 52, md: 58, lg: 64 }),
42 | },
43 | h2: {
44 | fontWeight: 800,
45 | lineHeight: 64 / 48,
46 | fontSize: pxToRem(32),
47 | ...responsiveFontSizes({ sm: 40, md: 44, lg: 48 }),
48 | },
49 | h3: {
50 | fontWeight: 700,
51 | lineHeight: 1.5,
52 | fontSize: pxToRem(24),
53 | ...responsiveFontSizes({ sm: 26, md: 30, lg: 32 }),
54 | },
55 | h4: {
56 | fontWeight: 700,
57 | lineHeight: 1.5,
58 | fontSize: pxToRem(20),
59 | ...responsiveFontSizes({ sm: 20, md: 24, lg: 24 }),
60 | },
61 | h5: {
62 | fontWeight: 700,
63 | lineHeight: 1.5,
64 | fontSize: pxToRem(18),
65 | ...responsiveFontSizes({ sm: 19, md: 20, lg: 20 }),
66 | },
67 | h6: {
68 | fontWeight: 700,
69 | lineHeight: 28 / 18,
70 | fontSize: pxToRem(17),
71 | ...responsiveFontSizes({ sm: 18, md: 18, lg: 18 }),
72 | },
73 | subtitle1: {
74 | fontWeight: 600,
75 | lineHeight: 1.5,
76 | fontSize: pxToRem(16),
77 | },
78 | subtitle2: {
79 | fontWeight: 600,
80 | lineHeight: 22 / 14,
81 | fontSize: pxToRem(14),
82 | },
83 | body1: {
84 | lineHeight: 1.5,
85 | fontSize: pxToRem(16),
86 | },
87 | body2: {
88 | lineHeight: 22 / 14,
89 | fontSize: pxToRem(14),
90 | },
91 | caption: {
92 | lineHeight: 1.5,
93 | fontSize: pxToRem(12),
94 | },
95 | overline: {
96 | fontWeight: 700,
97 | lineHeight: 1.5,
98 | fontSize: pxToRem(12),
99 | textTransform: 'uppercase',
100 | },
101 | button: {
102 | fontWeight: 700,
103 | lineHeight: 24 / 14,
104 | fontSize: pxToRem(14),
105 | textTransform: 'unset',
106 | },
107 | };
108 |
--------------------------------------------------------------------------------
/admin-panel/src/utils/format-number.js:
--------------------------------------------------------------------------------
1 | import numeral from 'numeral';
2 |
3 | export function fNumber(number) {
4 | return numeral(number).format();
5 | }
6 |
7 | export function fCurrency(number) {
8 | const format = number ? numeral(number).format('$0,0.00') : '';
9 |
10 | return result(format, '.00');
11 | }
12 |
13 | export function fPercent(number) {
14 | const format = number ? numeral(Number(number) / 100).format('0.0%') : '';
15 |
16 | return result(format, '.0');
17 | }
18 |
19 | export function fShortenNumber(number) {
20 | const format = number ? numeral(number).format('0.00a') : '';
21 |
22 | return result(format, '.00');
23 | }
24 |
25 | export function fData(number) {
26 | const format = number ? numeral(number).format('0.0 b') : '';
27 |
28 | return result(format, '.0');
29 | }
30 |
31 | function result(format, key = '.00') {
32 | const isInteger = format.includes(key);
33 |
34 | return isInteger ? format.replace(key, '') : format;
35 | }
36 |
--------------------------------------------------------------------------------
/admin-panel/src/utils/format-time.js:
--------------------------------------------------------------------------------
1 | import { format, getTime, formatDistanceToNow } from 'date-fns';
2 |
3 | // ----------------------------------------------------------------------
4 |
5 | export function fDate(date, newFormat) {
6 | const fm = newFormat || 'dd MMM yyyy';
7 |
8 | return date ? format(new Date(date), fm) : '';
9 | }
10 |
11 | export function fDateTime(date, newFormat) {
12 | const fm = newFormat || 'dd MMM yyyy p';
13 |
14 | return date ? format(new Date(date), fm) : '';
15 | }
16 |
17 | export function fTimestamp(date) {
18 | return date ? getTime(new Date(date)) : '';
19 | }
20 |
21 | export function fToNow(date) {
22 | return date
23 | ? formatDistanceToNow(new Date(date), {
24 | addSuffix: true,
25 | })
26 | : '';
27 | }
28 |
--------------------------------------------------------------------------------
/admin-panel/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "/"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/admin-panel/vite.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { defineConfig } from 'vite';
3 | import checker from 'vite-plugin-checker';
4 | import react from '@vitejs/plugin-react-swc';
5 |
6 | export default defineConfig(({ mode }) => {
7 | return {
8 | plugins: [
9 | react(),
10 | checker({
11 | eslint: {
12 | lintCommand: 'eslint "./src/**/*.{js,jsx,ts,tsx}"',
13 | },
14 | }),
15 | ],
16 | resolve: {
17 | alias: [
18 | {
19 | find: /^~(.+)/,
20 | replacement: path.join(process.cwd(), 'node_modules/$1'),
21 | },
22 | {
23 | find: /^src(.+)/,
24 | replacement: path.join(process.cwd(), 'src/$1'),
25 | },
26 | ],
27 | },
28 | server: {
29 | port: 3030,
30 | },
31 | preview: {
32 | port: 3030,
33 | },
34 | }
35 | });
36 |
--------------------------------------------------------------------------------
/client/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_API_URL=
--------------------------------------------------------------------------------
/client/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-detectable=false
2 | *.css linguist-detectable=false
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 | .yarn/install-state.gz
6 |
7 | # testing
8 | /coverage
9 |
10 | # next.js
11 | /.next/
12 | /out/
13 |
14 | # production
15 | /build
16 |
17 | # misc
18 | .DS_Store
19 | *.pem
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env*.local
28 |
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # contentlayer
39 | .contentlayer
40 |
41 | # prisma
42 | /prisma/.env
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | 🎨 QuickNotes Client 📝
2 |
3 |
4 | Frontend client for QuickNotes app. Provides an intuitive and responsive user interface for note-taking and management.
5 |
6 |
7 | ## 🛠️ Features
8 |
9 | - 📝 Effortlessly take and manage notes.
10 | - 🚀 Responsive and user-friendly UI.
11 | - 🛡️ Interacts with the backend server for authentication and data manipulation.
12 |
13 | ## 🛠️ Technologies Used
14 |
15 | - 💼 TypeScript for type safety.
16 | - 🌐 Next.js for server-side rendering.
17 | - 🎨 Tailwind CSS for styling.
18 | - 🌟 AOS for animations.
19 | - 🚀 Headless UI for some ready components.
20 |
21 | ## ℹ️ How to Use
22 |
23 | 1. 📦 Install dependencies by running:
24 | ```
25 | npm install
26 | ```
27 |
28 | 2. ⚙️ Set up your `.env` file:
29 | ```
30 | NEXT_PUBLIC_API_URL=
31 | ```
32 |
33 | 3. 🏃♂️ Run the app in development mode by executing:
34 | ```
35 | npm run dev
36 | ```
37 |
38 | 4. 🌐 Access the app in your browser at the provided URL (usually `http://localhost:3000`).
39 |
40 | 5. ⚠️ Make sure the backend is running. The backend is written in GoLang. You can find it in the [server](https://github.com/mutasim77/quick-notes/tree/master/server) folder.
41 |
42 |
43 | > Thank you for showing interest in this project. Happy coding! 🎉
44 |
--------------------------------------------------------------------------------
/client/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function AuthLayout({
2 | children,
3 | }: {
4 | children: React.ReactNode
5 | }) {
6 | return (
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/client/app/(auth)/reset-password/page.tsx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Reset Password - QuickNotes',
3 | description: 'Reset your Quick Notes account password securely and conveniently. Get back to managing your notes effortlessly!',
4 | keywords: ['reset password', 'Quick Notes', 'notes app', 'secure password reset'],
5 | author: 'Mutasim',
6 | }
7 |
8 | export default function ResetPassword() {
9 | return (
10 |
11 |
12 |
13 |
14 |
Let's get you back up on your feet
15 |
Enter the email address you used when you signed up for your account, and we'll email you a link to reset your password.
16 |
17 |
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/client/app/(auth)/signin/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import SignInForm from '@/components/ui/SIgnInForm'
3 |
4 | export const metadata = {
5 | title: 'Sign In - QuickNotes',
6 | description: 'Sign in to your QuickNotes account and start managing your notes effortlessly.',
7 | keywords: ['sign in', 'QuickNotes', 'notes app', 'authentication'],
8 | author: 'Mutasim',
9 | }
10 |
11 | export default function SignIn() {
12 | return (
13 |
14 |
15 |
16 |
17 |
Ready to get started? Sign in below
18 |
19 |
20 |
21 |
25 |
26 | Don't you have an account? Sign up
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/client/app/(auth)/signup/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import SignUpForm from '@/components/ui/SIgnUpForm'
3 |
4 | export const metadata = {
5 | title: 'Sign Up - QuickNotes',
6 | description: 'Create a new QuickNotes account and start jotting down your notes hassle-free.',
7 | keywords: ['sign up', 'QuickNotes', 'notes app', 'registration'],
8 | author: 'Mutasim',
9 | }
10 |
11 | export default function SignUp() {
12 | return (
13 |
14 |
15 |
16 |
17 |
Ready to join us? Sign up below
18 |
19 |
20 |
21 |
25 |
26 | Already using QuickNotes? Sign in
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/client/app/(default)/layout.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useEffect } from 'react'
4 |
5 | import AOS from 'aos'
6 | import 'aos/dist/aos.css'
7 |
8 | import Footer from '@/components/ui/Footer'
9 |
10 | export default function DefaultLayout({
11 | children,
12 | }: {
13 | children: React.ReactNode
14 | }) {
15 |
16 | useEffect(() => {
17 | AOS.init({
18 | once: true,
19 | disable: 'phone',
20 | duration: 700,
21 | easing: 'ease-out-cubic',
22 | })
23 | })
24 |
25 | return (
26 | <>
27 |
28 | {children}
29 |
30 |
31 | >
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/client/app/(default)/page.tsx:
--------------------------------------------------------------------------------
1 | import Hero from '@/components/Hero'
2 | import Features from '@/components/Features'
3 | import FeaturesBlocks from '@/components/FeaturesBlocks'
4 | import Testimonials from '@/components/Testimonials'
5 | import Newsletter from '@/components/Newsletter'
6 |
7 | export const metadata = {
8 | title: 'Home - QuickNotes',
9 | description: 'Welcome to QuickNotes - the easiest way to organize and manage your notes online.',
10 | keywords: ['QuickNotes', 'notes app', 'organization', 'productivity'],
11 | author: 'Mutasim',
12 | }
13 |
14 | export default function Home() {
15 | return (
16 | <>
17 |
18 |
19 |
20 |
21 |
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/client/app/css/additional-styles/range-slider.css:
--------------------------------------------------------------------------------
1 | /* Range slider */
2 | :root {
3 | --range-thumb-size: 36px;
4 | }
5 |
6 | input[type=range] {
7 | appearance: none;
8 | background: #ccc;
9 | border-radius: 3px;
10 | height: 6px;
11 | margin-top: (--range-thumb-size - 6px) * 0.5;
12 | margin-bottom: (--range-thumb-size - 6px) * 0.5;
13 | --thumb-size: #{--range-thumb-size};
14 | }
15 |
16 | input[type=range]::-webkit-slider-thumb {
17 | appearance: none;
18 | -webkit-appearance: none;
19 | background-color: #000;
20 | background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
21 | background-position: center;
22 | background-repeat: no-repeat;
23 | border: 0;
24 | border-radius: 50%;
25 | cursor: pointer;
26 | height: --range-thumb-size;
27 | width: --range-thumb-size;
28 | }
29 |
30 | input[type=range]::-moz-range-thumb {
31 | background-color: #000;
32 | background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
33 | background-position: center;
34 | background-repeat: no-repeat;
35 | border: 0;
36 | border: none;
37 | border-radius: 50%;
38 | cursor: pointer;
39 | height: --range-thumb-size;
40 | width: --range-thumb-size;
41 | }
42 |
43 | input[type=range]::-ms-thumb {
44 | background-color: #000;
45 | background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 .5v7L12 4zM0 4l4 3.5v-7z' fill='%23FFF' fill-rule='nonzero'/%3E%3C/svg%3E");
46 | background-position: center;
47 | background-repeat: no-repeat;
48 | border: 0;
49 | border-radius: 50%;
50 | cursor: pointer;
51 | height: --range-thumb-size;
52 | width: --range-thumb-size;
53 | }
54 |
55 | input[type=range]::-moz-focus-outer {
56 | border: 0;
57 | }
--------------------------------------------------------------------------------
/client/app/css/additional-styles/toggle-switch.css:
--------------------------------------------------------------------------------
1 | /* Switch element */
2 | .form-switch {
3 | @apply relative select-none;
4 | width: 68px;
5 | }
6 |
7 | .form-switch label {
8 | @apply block overflow-hidden cursor-pointer rounded;
9 | height: 38px;
10 | }
11 |
12 | .form-switch label>span:first-child {
13 | @apply absolute block rounded shadow;
14 | width: 30px;
15 | height: 30px;
16 | top: 4px;
17 | left: 4px;
18 | right: 50%;
19 | transition: all .15s ease-out;
20 | }
21 |
22 | .form-switch input[type="checkbox"]:checked+label {
23 | @apply bg-blue-600;
24 | }
25 |
26 | .form-switch input[type="checkbox"]:checked+label>span:first-child {
27 | left: 34px;
28 | }
29 |
--------------------------------------------------------------------------------
/client/app/css/additional-styles/utility-patterns.css:
--------------------------------------------------------------------------------
1 | /* Typography */
2 | .h1 {
3 | @apply text-4xl font-extrabold leading-tight tracking-tighter;
4 | }
5 |
6 | .h2 {
7 | @apply text-3xl font-extrabold leading-tight tracking-tighter;
8 | }
9 |
10 | .h3 {
11 | @apply text-3xl font-bold leading-tight;
12 | }
13 |
14 | .h4 {
15 | @apply text-2xl font-bold leading-snug tracking-tight;
16 | }
17 |
18 | @screen md {
19 | .h1 {
20 | @apply text-5xl;
21 | }
22 |
23 | .h2 {
24 | @apply text-4xl;
25 | }
26 | }
27 |
28 | /* Buttons */
29 | .btn,
30 | .btn-sm {
31 | @apply font-medium inline-flex items-center justify-center border border-transparent rounded leading-snug transition duration-150 ease-in-out;
32 | }
33 |
34 | .btn {
35 | @apply px-8 py-3 shadow-lg;
36 | }
37 |
38 | .btn-sm {
39 | @apply px-4 py-2 shadow;
40 | }
41 |
42 | /* Forms */
43 | .form-input,
44 | .form-textarea,
45 | .form-multiselect,
46 | .form-select,
47 | .form-checkbox,
48 | .form-radio {
49 | @apply bg-white border border-gray-300 focus:border-gray-500;
50 | }
51 |
52 | .form-input,
53 | .form-textarea,
54 | .form-multiselect,
55 | .form-select,
56 | .form-checkbox {
57 | @apply rounded;
58 | }
59 |
60 | .form-input,
61 | .form-textarea,
62 | .form-multiselect,
63 | .form-select {
64 | @apply py-3 px-4;
65 | }
66 |
67 | .form-input,
68 | .form-textarea {
69 | @apply placeholder-gray-500;
70 | }
71 |
72 | .form-select {
73 | @apply pr-10;
74 | }
75 |
76 | .form-checkbox,
77 | .form-radio {
78 | @apply text-gray-800 rounded-sm;
79 | }
--------------------------------------------------------------------------------
/client/app/css/style.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss/base';
2 | @import 'tailwindcss/components';
3 |
4 | @import 'additional-styles/utility-patterns.css';
5 | @import 'additional-styles/range-slider.css';
6 | @import 'additional-styles/toggle-switch.css';
7 | @import 'additional-styles/theme.css';
8 |
9 | @import 'tailwindcss/utilities';
10 |
11 | @layer utilities {
12 | .rtl {
13 | direction: rtl;
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/client/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './css/style.css'
2 |
3 | import { Inter } from 'next/font/google';
4 | import Header from '@/components/ui/Header';
5 | import { cookies } from 'next/headers';
6 |
7 | const inter = Inter({
8 | subsets: ['latin'],
9 | variable: '--font-inter',
10 | display: 'swap'
11 | })
12 |
13 | export const metadata = {
14 | title: 'QuickNotes - Organize Your Thoughts',
15 | description: 'QuickNotes is your ultimate tool for organizing and managing your thoughts, ideas, and tasks effortlessly. Get started for free today!',
16 | keywords: ['QuickNotes', 'notes app', 'organization', 'productivity'],
17 | author: 'Mutasim',
18 | }
19 |
20 | export default function RootLayout({
21 | children,
22 | }: {
23 | children: React.ReactNode
24 | }) {
25 |
26 | // TODO: Write actual logic here 👇 (preferably in middleware).
27 | const isAuth = !!(cookies().get('jwt')?.value);
28 | return (
29 |
30 |
31 |
32 |
33 | {children}
34 |
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/client/app/notes/page.tsx:
--------------------------------------------------------------------------------
1 | import AddNote from "@/components/notebook/AddNote";
2 | import DeleteNote from "@/components/notebook/DeleteNote";
3 | import { getNotes } from "@/utils/notes";
4 |
5 | interface INotes {
6 | id: number;
7 | title: string;
8 | text: string;
9 | tags: string;
10 | CreatedAt: string;
11 | }
12 |
13 | export default async function Notes() {
14 | const notes = await getNotes();
15 |
16 | return (
17 |
18 |
19 |
QuickNotes
20 |
21 |
22 |
23 |
24 |
25 | Filter
26 |
27 |
28 |
29 | {notes.map((note: INotes) => (
30 |
31 | ))}
32 |
33 |
34 | )
35 | }
36 |
37 | function Note({
38 | id,
39 | title,
40 | text,
41 | tags,
42 | CreatedAt
43 | }: INotes
44 | ) {
45 | const randomColor = ['bg-red-100', 'bg-green-100', 'bg-blue-100', 'bg-pink-200', 'bg-yellow-200', 'bg-indigo-200', 'bg-purple-200', 'bg-teal-200', 'bg-orange-200', 'bg-cyan-200', 'bg-lime-200', 'bg-amber-200',]
46 | const randomBgColor = randomColor[Math.floor(Math.random() * randomColor.length)];
47 |
48 | return (
49 |
50 |
58 |
59 |
{text}
60 |
61 | {tags.split(',').map((tag, index) => (
62 |
63 | {tag}
64 |
65 | ))}
66 |
67 |
68 |
69 | {CreatedAt.split('T')[0]}
70 |
71 |
72 |
73 | )
74 | }
--------------------------------------------------------------------------------
/client/components/FeaturesBlocks.tsx:
--------------------------------------------------------------------------------
1 | import { features } from "@/constants";
2 | import { FaLightbulb } from "react-icons/fa";
3 |
4 | export default function FeaturesBlocks() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Quick Notes Features 📜
13 |
Discover the rich array of features in Quick Notes for seamless, efficient note-taking and organization.
14 |
15 |
16 | {features.map((feature, inx) => (
17 |
18 |
19 |
{feature.title}
20 |
{feature.description}
21 |
22 | ))}
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/client/components/Hero.tsx:
--------------------------------------------------------------------------------
1 | import VideoThumb from '@/public/images/hero-image.png'
2 | import ModalVideo from '@/components/ModalVideo'
3 |
4 | export default function Hero() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Turn your Thoughts into
29 | QuickNotes
30 |
31 |
32 |
33 | Take notes effortlessly and access them from anywhere. Our app simplifies note-taking, making it easy to organize your thoughts and ideas on the go. 🦋
34 |
35 |
43 |
44 |
45 |
53 |
54 |
55 |
56 | )
57 | }
--------------------------------------------------------------------------------
/client/components/Newsletter.tsx:
--------------------------------------------------------------------------------
1 | export default function Newsletter() {
2 | return (
3 |
4 |
5 |
6 |
7 | {/* CTA box */}
8 |
9 |
10 | {/* Background illustration */}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Looking for additional resources?
39 |
Explore our collection of tutorials and guides to enhance your productivity and efficiency.
40 |
41 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | )
60 | }
--------------------------------------------------------------------------------
/client/components/notebook/DeleteNote.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { FaTrash } from "react-icons/fa";
4 | import { useRouter } from "next/navigation";
5 |
6 | export default function DeleteNote({ noteID }: { noteID: number }) {
7 | const router = useRouter();
8 |
9 | async function deleteNote(noteID: number) {
10 | try {
11 | const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/note/${noteID}`, {
12 | method: 'DELETE',
13 | credentials: 'include',
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | },
17 | });
18 |
19 | if (!res.ok) {
20 | throw new Error('Failed to delete note');
21 | }
22 | router.refresh();
23 | } catch (error) {
24 | throw new Error(`Error deleting note: ${error}`);
25 | }
26 | }
27 |
28 | return (
29 | deleteNote(noteID)}
31 | className="text-red-500 cursor-pointer text-xl"
32 | />
33 | )
34 | }
--------------------------------------------------------------------------------
/client/components/ui/Header.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from 'next/link';
4 | import Logo from './Logo';
5 | import MobileMenu from './MobileMenu';
6 | import { useState, useEffect } from 'react';
7 | import { useRouter } from 'next/navigation';
8 |
9 | export default function Header({ isAuth }: { isAuth: boolean }) {
10 | const router = useRouter();
11 | const [top, setTop] = useState(true);
12 |
13 | const scrollHandler = () => window.scrollY > 10 ? setTop(false) : setTop(true);
14 |
15 | useEffect(() => {
16 | scrollHandler()
17 | window.addEventListener('scroll', scrollHandler);
18 | return () => {
19 | window.removeEventListener('scroll', scrollHandler);
20 | }
21 | }, [top]);
22 |
23 | const logout = async () => {
24 | await fetch(`${process.env.NEXT_PUBLIC_API_URL}/logout`, {
25 | method: 'POST',
26 | headers: { 'Content-Type': 'application/json' },
27 | credentials: 'include',
28 | });
29 |
30 | router.refresh();
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {isAuth ?
43 |
44 |
49 | Logout
50 |
51 |
52 | :
53 | <>
54 |
55 |
56 | Sign in
57 |
58 |
59 |
60 |
61 | Sign up
62 |
63 |
64 |
65 |
66 |
67 | >
68 | }
69 |
70 |
71 |
72 |
73 |
74 |
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/client/components/ui/Logo.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | export default function Logo() {
4 | return (
5 |
6 |
7 | QuickNotes 📝
8 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/client/components/ui/MobileMenu.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState, useRef, useEffect } from 'react'
4 | import { Transition } from '@headlessui/react'
5 | import Link from 'next/link'
6 |
7 | export default function MobileMenu() {
8 | const [mobileNavOpen, setMobileNavOpen] = useState(false)
9 |
10 | const trigger = useRef(null)
11 | const mobileNav = useRef(null)
12 |
13 | // close the mobile menu on click outside
14 | useEffect(() => {
15 | const clickHandler = ({ target }: { target: EventTarget | null }): void => {
16 | if (!mobileNav.current || !trigger.current) return;
17 | if (!mobileNavOpen || mobileNav.current.contains(target as Node) || trigger.current.contains(target as Node)) return;
18 | setMobileNavOpen(false)
19 | };
20 | document.addEventListener('click', clickHandler)
21 | return () => document.removeEventListener('click', clickHandler)
22 | })
23 |
24 | // close the mobile menu if the esc key is pressed
25 | useEffect(() => {
26 | const keyHandler = ({ keyCode }: { keyCode: number }): void => {
27 | if (!mobileNavOpen || keyCode !== 27) return;
28 | setMobileNavOpen(false)
29 | };
30 | document.addEventListener('keydown', keyHandler)
31 | return () => document.removeEventListener('keydown', keyHandler)
32 | })
33 |
34 | return (
35 |
36 | {/* Hamburger button */}
37 |
setMobileNavOpen(!mobileNavOpen)}
43 | >
44 | Menu
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {/*Mobile navigation */}
53 |
54 |
66 |
67 |
68 | setMobileNavOpen(false)}>Sign in
69 |
70 |
71 | setMobileNavOpen(false)}>
72 | Sign up
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/client/components/ui/SIgnInForm.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from "next/link";
4 | import { useRouter } from "next/navigation";
5 | import { FormEvent } from "react";
6 |
7 | export default function SignInForm() {
8 | const router = useRouter()
9 |
10 | const handleSubmit = async (event: FormEvent) => {
11 | event.preventDefault()
12 |
13 | const formData = new FormData(event.currentTarget);
14 | const email = formData.get('email');
15 | const password = formData.get('password');
16 |
17 | const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/login`, {
18 | method: 'POST',
19 | headers: {
20 | 'Content-Type': 'application/json'
21 | },
22 | credentials: 'include',
23 | body: JSON.stringify({ email, password }),
24 | })
25 |
26 | if (response.ok) {
27 | router.push('/notes');
28 | } else {
29 | console.log(response);
30 | }
31 | }
32 |
33 | return (
34 |
64 | )
65 |
66 | }
--------------------------------------------------------------------------------
/client/components/ui/SIgnUpForm.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from "next/link";
4 | import { useRouter } from "next/navigation";
5 | import { FormEvent } from "react";
6 |
7 | export default function SignUpForm() {
8 | const router = useRouter()
9 |
10 | const handleSubmit = async (event: FormEvent) => {
11 | event.preventDefault()
12 |
13 | const formData = new FormData(event.currentTarget);
14 | const email = formData.get('email');
15 | const password = formData.get('password');
16 | const name = formData.get('name');
17 |
18 | const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/register`, {
19 | method: 'POST',
20 | headers: {
21 | 'Content-Type': 'application/json'
22 | },
23 | body: JSON.stringify({ email, name, password }),
24 | })
25 |
26 | if (response.ok) {
27 | router.push('/notes');
28 | } else {
29 | console.log(response);
30 | }
31 | }
32 |
33 | return (
34 |
62 | )
63 |
64 | }
--------------------------------------------------------------------------------
/client/constants/index.ts:
--------------------------------------------------------------------------------
1 | import { IconType } from "react-icons";
2 | import { FaLightbulb } from "react-icons/fa";
3 | import { GrSecure } from "react-icons/gr";
4 | import { GoGoal } from "react-icons/go";
5 | import { GiProgression, GiBookshelf, GiClover } from "react-icons/gi";
6 |
7 | interface Feature {
8 | title: string;
9 | description: string;
10 | logo: IconType;
11 | }
12 |
13 | export const features: Feature[] = [
14 | {
15 | title: "💡 Smart Reminders",
16 | description: "Stay On Top of Your Tasks. Never forget an important deadline again.",
17 | logo: FaLightbulb
18 | },
19 | {
20 | title: "🔒 Secure Encryption",
21 | description: "Protect Your Private Notes. Keep your thoughts safe and secure.",
22 | logo: GrSecure
23 | },
24 | {
25 | title: "📈 Progress Tracking",
26 | description: "Monitor Your Academic Journey. Stay motivated with progress insights.",
27 | logo: GiProgression
28 | },
29 | {
30 | title: "📚 Integrated Learning",
31 | description: "Sync Your Study Materials. Seamlessly integrate with your classes.",
32 | logo: GiBookshelf
33 | },
34 | {
35 | title: "🌟 Inspirational Quotes",
36 | description: "Find Daily Motivation. Fuel your inspiration every day.",
37 | logo: GiClover
38 | },
39 | {
40 | title: "🎯 Goal Setting",
41 | description: "Set and Achieve Your Objectives. Keep your goals organized and on track.",
42 | logo: GoGoal
43 | }
44 | ];
45 |
--------------------------------------------------------------------------------
/client/middleware.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import type { NextRequest } from "next/server";
3 |
4 |
5 | const protectedRoutes = ["/notes"];
6 |
7 | // TODO: Write actual logic here 👇
8 | export function middleware(req: NextRequest) {
9 | const currentUser = req.cookies.get('jwt')?.value;
10 |
11 | if (!currentUser && protectedRoutes.includes(req.nextUrl.pathname)) {
12 | return NextResponse.redirect(new URL('/signin', req.nextUrl.origin));
13 | }
14 | }
--------------------------------------------------------------------------------
/client/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quick-notes",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start",
8 | "lint": "next lint"
9 | },
10 | "dependencies": {
11 | "@headlessui/react": "^1.7.18",
12 | "@types/node": "^20.10.4",
13 | "@types/react": "^18.2.42",
14 | "@types/react-dom": "^18.2.17",
15 | "aos": "^3.0.0-beta.6",
16 | "next": "^14.0.4",
17 | "react": "18.2.0",
18 | "react-dom": "18.2.0",
19 | "react-icons": "^5.0.1",
20 | "typescript": "^5.3.3"
21 | },
22 | "devDependencies": {
23 | "@tailwindcss/forms": "^0.5.7",
24 | "@types/aos": "^3.0.7",
25 | "autoprefixer": "^10.4.16",
26 | "eslint": "8.57.0",
27 | "eslint-config-next": "14.1.4",
28 | "postcss": "^8.4.32",
29 | "tailwindcss": "^3.3.6"
30 | }
31 | }
--------------------------------------------------------------------------------
/client/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-import': {},
4 | tailwindcss: {},
5 | autoprefixer: {},
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/images/anime.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/images/anime.jpeg
--------------------------------------------------------------------------------
/client/public/images/feature1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/images/feature1.jpeg
--------------------------------------------------------------------------------
/client/public/images/feature2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/images/feature2.jpeg
--------------------------------------------------------------------------------
/client/public/images/feature3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/images/feature3.jpeg
--------------------------------------------------------------------------------
/client/public/images/hero-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/images/hero-image.png
--------------------------------------------------------------------------------
/client/public/videos/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mutasim77/quick-notes/5224ebc81d3a6902c30d724a43a6a854874d0f2e/client/public/videos/video.mp4
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/client/utils/notes.ts:
--------------------------------------------------------------------------------
1 | import { cookies } from "next/headers";
2 |
3 | export async function getNotes() {
4 | try {
5 | const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/notes`, {
6 | headers: { Cookie: cookies().toString() },
7 | });
8 |
9 | if (!res.ok) {
10 | throw new Error('Failed to fetch notes');
11 | }
12 |
13 | return res.json();
14 | } catch (error) {
15 | console.error('Error fetching notes:', error);
16 | throw error;
17 | }
18 | }
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | SERVER_DIR = server
2 | CLIENT_DIR = client
3 | ADMIN_PANEL_DIR = admin-panel
4 |
5 | install:
6 | @echo "Installing server dependencies..."
7 | cd $(SERVER_DIR) && go mod download
8 | @echo "Installing client dependencies..."
9 | cd $(CLIENT_DIR) && npm install
10 | @echo "Installing admin panel dependencies..."
11 | cd $(ADMIN_PANEL_DIR) && yarn
12 |
13 | run-server:
14 | @echo "Running server..."
15 | cd $(SERVER_DIR)/cmd && go run main.go
16 |
17 | run-client:
18 | @echo "Running client..."
19 | cd $(CLIENT_DIR) && npm run dev
20 |
21 | run-admin-panel:
22 | @echo "Running admin panel..."
23 | cd $(ADMIN_PANEL_DIR) && yarn run dev
24 |
25 | .PHONY: install run-server run-client run-admin-panel
26 |
--------------------------------------------------------------------------------
/server/.env.example:
--------------------------------------------------------------------------------
1 | #*---Database Configuration---
2 | DB_HOST=
3 | DB_USER=
4 | DB_PASSWORD=
5 | DB_NAME=
6 | DB_PORT=
7 |
8 | #*---CORS Configuration---
9 | CORS_WHITELIST=
10 |
11 | #*---JWT Configurations----
12 | JWT_SECRET_KEY=
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Go workspace file
15 | go.work
16 |
17 | # local env files
18 | .env*.local
19 |
20 | .env
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | 🚀 QuickNotes Server 📡
2 |
3 |
4 | Backend server for QuickNotes app. Handles JWT token-based authentication, role-based protected routes, and database manipulation.
5 |
6 |
7 | ## 🛠️ Features
8 |
9 | - 🔐 JWT token-based authentication and authorization.
10 | - 🔒 Role-based protected routes for admin.
11 | - 🛡️ Secure and protected routes using middleware.
12 | - 📦 Adding data to the database (PostgreSQL).
13 |
14 | ## 💻 Technologies Used
15 |
16 | - 🐍 Golang for writing backend apps.
17 | - 🔑 jwt-go for generating JWT tokens (app is token-based auth).
18 | - 🔌 Fiber for making fast webservers and easy-to-play-with middleware.
19 | - 🛠️ Gorm ORM for database manipulation.
20 |
21 | ## ℹ️ How to Use
22 |
23 | 1. 📝 Set up the same `.env` file by copying `.env.example` and providing the required configurations:
24 | ```dotenv
25 | #---Database Configuration---
26 | DB_HOST=
27 | DB_USER=
28 | DB_PASSWORD=
29 | DB_NAME=
30 | DB_PORT=
31 |
32 | #---CORS Configuration---
33 | CORS_WHITELIST=
34 |
35 | #---JWT Configurations----
36 | JWT_SECRET_KEY=
37 | ```
38 |
39 | 2. 🏃♂️ Navigate to the `cmd` folder.
40 |
41 | 3. ⚙️ Run the server by executing:
42 | ```
43 | go run main.go
44 | ```
45 |
46 | 4. ✔️ If everything is configured correctly, you will see successful connection to the database and successful database migration.
47 |
48 | 5. 🚀 Now it's time to navigate to the [client](https://github.com/mutasim77/quick-notes/tree/master/client) folder and run the client, which is written in Next.js. (Or you can write your own client app and connect to this URL).
49 |
50 | > Thank you for showing interest in this project. Happy coding! 🎉
51 |
--------------------------------------------------------------------------------
/server/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | "quick-notes/database"
7 | "quick-notes/middleware"
8 | "quick-notes/routes"
9 |
10 | "github.com/gofiber/fiber/v2"
11 | "github.com/gofiber/fiber/v2/middleware/cors"
12 | "github.com/joho/godotenv"
13 | )
14 |
15 | func main() {
16 | // Load environment variables
17 | if err := godotenv.Load("../.env"); err != nil && !os.IsNotExist(err) {
18 | log.Fatalf("Error loading .env")
19 | }
20 |
21 | database.Connect()
22 |
23 | app := fiber.New()
24 |
25 | app.Use(cors.New(cors.Config{
26 | AllowCredentials: true,
27 | AllowOrigins: os.Getenv("CORS_WHITELIST"),
28 | }))
29 |
30 | app.Use("/api/note", middleware.AuthMiddleware)
31 | app.Use("/api/admin/*", middleware.CheckAccessLevel)
32 |
33 | routes.AuthRoutes(app)
34 | routes.NotesRoutes(app)
35 | routes.AdminRoutes(app)
36 |
37 | app.Listen(":8000")
38 | }
39 |
--------------------------------------------------------------------------------
/server/controllers/auth.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "os"
5 | "quick-notes/database"
6 | "quick-notes/models"
7 | "strconv"
8 | "time"
9 |
10 | "golang.org/x/crypto/bcrypt"
11 |
12 | "github.com/dgrijalva/jwt-go"
13 | "github.com/gofiber/fiber/v2"
14 | )
15 |
16 | type CustomClaims struct {
17 | jwt.StandardClaims
18 | Role string `json:"role"`
19 | }
20 |
21 | // Register handles user registration
22 | func Register(c *fiber.Ctx) error {
23 | var data map[string]string
24 |
25 | if err := c.BodyParser(&data); err != nil {
26 | return err
27 | }
28 |
29 | password, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 14)
30 |
31 | user := models.User{
32 | Name: data["name"],
33 | Email: data["email"],
34 | Password: password,
35 | }
36 |
37 | database.DB.Create(&user)
38 |
39 | return c.JSON(user)
40 | }
41 |
42 | // Login handles user login
43 | func Login(c *fiber.Ctx) error {
44 | var data map[string]string
45 |
46 | if err := c.BodyParser(&data); err != nil {
47 | return err
48 | }
49 |
50 | var user models.User
51 |
52 | database.DB.Where("email = ?", data["email"]).First(&user)
53 |
54 | if user.Id == 0 {
55 | c.Status(fiber.StatusNotFound)
56 | return c.JSON(fiber.Map{
57 | "message": "user not found",
58 | })
59 | }
60 |
61 | if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])); err != nil {
62 | c.Status(fiber.StatusBadRequest)
63 | return c.JSON(fiber.Map{
64 | "message": "incorrect password",
65 | })
66 | }
67 |
68 | claims := CustomClaims{
69 | StandardClaims: jwt.StandardClaims{
70 | Issuer: strconv.Itoa(int(user.Id)),
71 | ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), // 1 day
72 | },
73 | Role: user.Role,
74 | }
75 |
76 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
77 |
78 | jwtSecretKey := os.Getenv("JWT_SECRET_KEY")
79 | tokenString, err := token.SignedString([]byte(jwtSecretKey))
80 | if err != nil {
81 | c.Status(fiber.StatusInternalServerError)
82 | return c.JSON(fiber.Map{
83 | "message": "could not login",
84 | })
85 | }
86 |
87 | cookie := fiber.Cookie{
88 | Name: "jwt",
89 | Value: tokenString,
90 | Expires: time.Now().Add(time.Hour * 24),
91 | HTTPOnly: true,
92 | }
93 |
94 | c.Cookie(&cookie)
95 |
96 | return c.JSON(fiber.Map{
97 | "message": "success",
98 | })
99 | }
100 |
101 | func Logout(c *fiber.Ctx) error {
102 | cookie := fiber.Cookie{
103 | Name: "jwt",
104 | Value: "",
105 | Expires: time.Now().Add(-time.Hour),
106 | HTTPOnly: true,
107 | }
108 |
109 | c.Cookie(&cookie)
110 |
111 | return c.JSON(fiber.Map{
112 | "message": "success",
113 | })
114 | }
115 |
116 | func User(c *fiber.Ctx) error {
117 | cookie := c.Cookies("jwt")
118 |
119 | jwtSecretKey := os.Getenv("JWT_SECRET_KEY")
120 | token, err := jwt.ParseWithClaims(cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
121 | return []byte(jwtSecretKey), nil
122 | })
123 |
124 | if err != nil {
125 | c.Status(fiber.StatusUnauthorized)
126 | return c.JSON(fiber.Map{
127 | "message": "unauthenticated",
128 | })
129 | }
130 |
131 | claims := token.Claims.(*jwt.StandardClaims)
132 |
133 | var user models.User
134 |
135 | database.DB.Where("id = ?", claims.Issuer).First(&user)
136 |
137 | return c.JSON(user)
138 | }
139 |
140 | func GetAllUsers(c *fiber.Ctx) error {
141 | var users []models.User
142 | database.DB.Find(&users)
143 |
144 | return c.JSON(users)
145 | }
146 |
--------------------------------------------------------------------------------
/server/controllers/notes.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "quick-notes/database"
5 | "quick-notes/models"
6 |
7 | "github.com/gofiber/fiber/v2"
8 | )
9 |
10 | func CreateNote(c *fiber.Ctx) error {
11 | var data map[string]string
12 |
13 | if err := c.BodyParser(&data); err != nil {
14 | return err
15 | }
16 |
17 | userID := c.Locals("userID")
18 |
19 | note := models.Note{
20 | UserID: userID.(int),
21 | Title: data["title"],
22 | Text: data["text"],
23 | Tags: data["tags"],
24 | }
25 |
26 | database.DB.Create(¬e)
27 |
28 | return c.JSON(note)
29 | }
30 |
31 | func GetNotes(c *fiber.Ctx) error {
32 | userID := c.Locals("userID").(int)
33 |
34 | var notes []models.Note
35 | database.DB.Where("user_id = ?", userID).Find(¬es)
36 |
37 | return c.JSON(notes)
38 | }
39 |
40 | func DeleteNote(c *fiber.Ctx) error {
41 | userID := c.Locals("userID").(int)
42 | noteID := c.Params("id")
43 |
44 | //! Check if the note exists and belongs to the authenticated user
45 | var note models.Note
46 | if err := database.DB.Where("id = ? AND user_id = ?", noteID, userID).First(¬e).Error; err != nil {
47 | return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
48 | "message": "Note not found",
49 | })
50 | }
51 |
52 | //! Delete the note from the database
53 | if err := database.DB.Delete(¬e).Error; err != nil {
54 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
55 | "message": "Failed to delete note",
56 | })
57 | }
58 |
59 | return c.JSON(fiber.Map{
60 | "message": "Note deleted successfully",
61 | })
62 | }
63 |
64 | func GetAllNotes(c *fiber.Ctx) error {
65 | var notes []models.Note
66 | database.DB.Find(¬es)
67 |
68 | return c.JSON(notes)
69 | }
70 |
--------------------------------------------------------------------------------
/server/database/connection.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "quick-notes/models"
8 |
9 | "gorm.io/driver/postgres"
10 | "gorm.io/gorm"
11 | )
12 |
13 | var DB *gorm.DB
14 |
15 | func Connect() {
16 | DSN := GetDSN()
17 | db, err := gorm.Open(postgres.Open(DSN), &gorm.Config{})
18 |
19 | if err != nil {
20 | panic("Couldn't connect to the database")
21 | }
22 | log.Printf("The database has been connected successfully 🥰")
23 |
24 | DB = db
25 |
26 | MigrateDatabase()
27 | }
28 |
29 | func MigrateDatabase() {
30 | db := DB
31 | err := db.AutoMigrate(
32 | &models.User{},
33 | &models.Note{},
34 | )
35 |
36 | if err != nil {
37 | log.Fatalf("Failed to migrate database schema: %v", err)
38 | }
39 |
40 | log.Println("Database schema migrated successfully 🤩")
41 | }
42 |
43 | func GetDSN() string {
44 | host := os.Getenv("DB_HOST")
45 | user := os.Getenv("DB_USER")
46 | password := os.Getenv("DB_PASSWORD")
47 | dbname := os.Getenv("DB_NAME")
48 | port := os.Getenv("DB_PORT")
49 |
50 | return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s", host, user, password, dbname, port)
51 | }
52 |
--------------------------------------------------------------------------------
/server/go.mod:
--------------------------------------------------------------------------------
1 | module quick-notes
2 |
3 | go 1.21.6
4 |
5 | require (
6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
7 | github.com/gofiber/fiber/v2 v2.52.2
8 | github.com/joho/godotenv v1.5.1
9 | golang.org/x/crypto v0.21.0
10 | gorm.io/driver/postgres v1.5.7
11 | gorm.io/gorm v1.25.8
12 | )
13 |
14 | require (
15 | github.com/andybalholm/brotli v1.1.0 // indirect
16 | github.com/google/uuid v1.6.0 // indirect
17 | github.com/jackc/pgpassfile v1.0.0 // indirect
18 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
19 | github.com/jackc/pgx/v5 v5.4.3 // indirect
20 | github.com/jinzhu/inflection v1.0.0 // indirect
21 | github.com/jinzhu/now v1.1.5 // indirect
22 | github.com/klauspost/compress v1.17.7 // indirect
23 | github.com/mattn/go-colorable v0.1.13 // indirect
24 | github.com/mattn/go-isatty v0.0.20 // indirect
25 | github.com/mattn/go-runewidth v0.0.15 // indirect
26 | github.com/rivo/uniseg v0.2.0 // indirect
27 | github.com/stretchr/testify v1.9.0 // indirect
28 | github.com/valyala/bytebufferpool v1.0.0 // indirect
29 | github.com/valyala/fasthttp v1.52.0 // indirect
30 | github.com/valyala/tcplisten v1.0.0 // indirect
31 | golang.org/x/sys v0.18.0 // indirect
32 | golang.org/x/text v0.14.0 // indirect
33 | )
34 |
--------------------------------------------------------------------------------
/server/middleware/auth.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "os"
5 | "strconv"
6 |
7 | "github.com/dgrijalva/jwt-go"
8 | "github.com/gofiber/fiber/v2"
9 | )
10 |
11 | func AuthMiddleware(c *fiber.Ctx) error {
12 | // Extract JWT token from cookie
13 | cookie := c.Cookies("jwt")
14 | if cookie == "" {
15 | c.Status(fiber.StatusUnauthorized)
16 | return c.JSON(fiber.Map{
17 | "message": "missing JWT token",
18 | })
19 | }
20 |
21 | // Parse JWT token
22 | jwtSecretKey := os.Getenv("JWT_SECRET_KEY")
23 | token, err := jwt.ParseWithClaims(cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
24 | return []byte(jwtSecretKey), nil
25 | })
26 | if err != nil || !token.Valid {
27 | c.Status(fiber.StatusUnauthorized)
28 | return c.JSON(fiber.Map{
29 | "message": "invalid JWT token",
30 | })
31 | }
32 |
33 | // Extract user ID from JWT claims
34 | claims, ok := token.Claims.(*jwt.StandardClaims)
35 | if !ok {
36 | c.Status(fiber.StatusInternalServerError)
37 | return c.JSON(fiber.Map{
38 | "message": "failed to parse JWT claims",
39 | })
40 | }
41 |
42 | userID, err := strconv.Atoi(claims.Issuer)
43 | if err != nil {
44 | c.Status(fiber.StatusInternalServerError)
45 | return c.JSON(fiber.Map{
46 | "message": "failed to parse user ID",
47 | })
48 | }
49 |
50 | // Set user ID in request *context*
51 | c.Locals("userID", userID)
52 |
53 | return c.Next()
54 | }
55 |
--------------------------------------------------------------------------------
/server/middleware/role.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "os"
5 | "quick-notes/controllers"
6 |
7 | "github.com/dgrijalva/jwt-go"
8 | "github.com/gofiber/fiber/v2"
9 | )
10 |
11 | func CheckAccessLevel(c *fiber.Ctx) error {
12 | cookie := c.Cookies("jwt")
13 | if cookie == "" {
14 | c.Status(fiber.StatusUnauthorized)
15 | return c.JSON(fiber.Map{
16 | "message": "missing JWT token",
17 | })
18 | }
19 |
20 | jwtSecretKey := os.Getenv("JWT_SECRET_KEY")
21 | token, err := jwt.ParseWithClaims(cookie, &controllers.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
22 | return []byte(jwtSecretKey), nil
23 | })
24 | if err != nil || !token.Valid {
25 | c.Status(fiber.StatusUnauthorized)
26 | return c.JSON(fiber.Map{
27 | "message": "invalid JWT token",
28 | })
29 | }
30 |
31 | claims, ok := token.Claims.(*controllers.CustomClaims)
32 | if !ok {
33 | c.Status(fiber.StatusInternalServerError)
34 | return c.JSON(fiber.Map{
35 | "message": "failed to parse JWT claims",
36 | })
37 | }
38 |
39 | userRole := claims.Role
40 |
41 | if userRole == "user" {
42 | c.Status(fiber.StatusForbidden)
43 | return c.JSON(fiber.Map{
44 | "message": "user is not an admin",
45 | })
46 | }
47 |
48 | c.Locals("UserRole", userRole)
49 |
50 | return c.Next()
51 | }
52 |
--------------------------------------------------------------------------------
/server/models/note.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "gorm.io/gorm"
5 | )
6 |
7 | type Note struct {
8 | gorm.Model
9 | Id uint `json:"id"`
10 | UserID int `json:"user_id"`
11 | Title string `json:"title"`
12 | Text string `json:"text"`
13 | Tags string `json:"tags"`
14 | }
15 |
--------------------------------------------------------------------------------
/server/models/user.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "gorm.io/gorm"
4 |
5 | type User struct {
6 | gorm.Model
7 | Id uint `json:"id"`
8 | Name string `json:"name"`
9 | Email string `json:"email" gorm:"unique"`
10 | Role string `json:"role"`
11 | Password []byte `json:"-"`
12 | }
13 |
14 | /**
15 | The field is tagged with "-" to indicate that it should be excluded
16 | from JSON serialization and deserialization. This is commonly used for sensitive information like passwords
17 | to prevent them from being inadvertently exposed in JSON responses or stored in plain sight.
18 | **/
19 |
--------------------------------------------------------------------------------
/server/routes/admin.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "quick-notes/controllers"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | )
8 |
9 | func AdminRoutes(app *fiber.App) {
10 | app.Get("/api/admin/users", controllers.GetAllUsers)
11 | app.Get("/api/admin/notes", controllers.GetAllNotes)
12 | }
13 |
--------------------------------------------------------------------------------
/server/routes/auth.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "quick-notes/controllers"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | )
8 |
9 | func AuthRoutes(app *fiber.App) {
10 | app.Post("/api/register", controllers.Register)
11 | app.Post("/api/login", controllers.Login)
12 | app.Post("/api/logout", controllers.Logout)
13 |
14 | app.Get("/api/user", controllers.User)
15 | }
16 |
--------------------------------------------------------------------------------
/server/routes/notes.go:
--------------------------------------------------------------------------------
1 | package routes
2 |
3 | import (
4 | "quick-notes/controllers"
5 |
6 | "github.com/gofiber/fiber/v2"
7 | )
8 |
9 | func NotesRoutes(app *fiber.App) {
10 | app.Post("/api/note", controllers.CreateNote)
11 | app.Get("/api/notes", controllers.GetNotes)
12 | app.Delete("/api/note/:id", controllers.DeleteNote)
13 | }
14 |
--------------------------------------------------------------------------------