diff --git a/admin-client/.gitignore b/admin-client/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/admin-client/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/admin-client/.vscode/settings.json b/admin-client/.vscode/settings.json
new file mode 100644
index 0000000..30de70f
--- /dev/null
+++ b/admin-client/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "editor.acceptSuggestionOnEnter": "on"
+}
\ No newline at end of file
diff --git a/admin-client/README.md b/admin-client/README.md
new file mode 100644
index 0000000..ce3d0bc
--- /dev/null
+++ b/admin-client/README.md
@@ -0,0 +1,39 @@
+## DEPLOYED AT
+https://adnans-react-typescript-todo.netlify.app/
+
+
+credentials:
+
+username: "AdnanKhan"
+password: "AdnanKhan@4069"
+
+
+Frontend Engineer Assignment
+
+Create a React Application with a login page and a dashboard page. The
+dashboard should display the user’s profile information and a list of the user’s
+todos.
+For authentication, user profile info You can use a JSON file or a static
+JavaScript object for mock username & password data.
+Implement the following features in the application:
+1. Implement user authentication using a secure token-based authentication
+such as JWT. Without login, the user should not be able to see the dashboard.
+2. The app should use React Context API to store login user information.
+3. Implement the logout functionality.
+4. In the dashboard, add a button through which new Todos can be added,
+when creating a todo it can be stored in a global context.
+5. Add a feature that allows the addition of Nested Todo, (Sub Task), It is
+entirely up to you how you design this feature. get inspiration from existing
+Todo apps
+6. Make sure to sanitize and validate user inputs to prevent injection attacks.
+
+7. Implement a route guard that requires the user to be successfully
+authenticated before they can view their dashboard. If the user is not
+authenticated, they should be redirected to the login page.
+8. Implement proper error handling in the code.
+9. Use your own creativity to make a better user experience.
+10. The app can be hosted on any platform i.e Netlify, or Vercel. And source
+code should be publically accessible on any git repository to review.
+11. The source code must be in Typescript.
+Candidates can take inspiration from this screenshot below to create their own
+dashboard experience.
\ No newline at end of file
diff --git a/admin-client/package.json b/admin-client/package.json
new file mode 100644
index 0000000..1f71730
--- /dev/null
+++ b/admin-client/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "todo-app",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@chakra-ui/icons": "^2.1.0",
+ "@chakra-ui/react": "^2.8.0",
+ "@emotion/react": "^11.11.1",
+ "@emotion/styled": "^11.11.0",
+ "@mui/material": "^5.13.7",
+ "@mui/styled-engine-sc": "^5.12.0",
+ "@reduxjs/toolkit": "^1.9.5",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^13.4.0",
+ "@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^27.5.2",
+ "@types/node": "^16.18.35",
+ "@types/node-sass": "^4.11.3",
+ "@types/react": "^18.2.14",
+ "@types/react-dom": "^18.2.6",
+ "@types/react-redux": "^7.1.25",
+ "framer-motion": "^10.12.16",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-redux": "^8.1.1",
+ "react-router-dom": "^6.12.1",
+ "react-scripts": "^5.0.1",
+ "redux-thunk": "^2.4.2",
+ "sass": "^1.64.1",
+ "styled-components": "^5.3.11",
+ "typescript": "^4.9.5",
+ "web-vitals": "^2.1.4"
+ },
+ "scripts": {
+ "start": "ENV=LOCAL react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/admin-client/public/_redirects b/admin-client/public/_redirects
new file mode 100644
index 0000000..f824337
--- /dev/null
+++ b/admin-client/public/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
\ No newline at end of file
diff --git a/admin-client/public/favicon.ico b/admin-client/public/favicon.ico
new file mode 100644
index 0000000..2911301
Binary files /dev/null and b/admin-client/public/favicon.ico differ
diff --git a/admin-client/public/index.html b/admin-client/public/index.html
new file mode 100644
index 0000000..3ada0ec
--- /dev/null
+++ b/admin-client/public/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+ Todo App
+
+
+
+
+
+
+
diff --git a/admin-client/public/manifest.json b/admin-client/public/manifest.json
new file mode 100644
index 0000000..1f2f141
--- /dev/null
+++ b/admin-client/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/admin-client/public/robots.txt b/admin-client/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/admin-client/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/admin-client/src/App.scss b/admin-client/src/App.scss
new file mode 100644
index 0000000..13e4b0d
--- /dev/null
+++ b/admin-client/src/App.scss
@@ -0,0 +1,273 @@
+@import url('https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@100;300;400;500;700;800;900&family=Meera+Inimai&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Signika:wght@300;400;500;600;700&family=Varela+Round&display=swap');
+
+@import './styles/typography/typography.scss';
+$black1: #000000;
+$black2: #303030;
+$black3: #606060;
+$black4: #909090;
+$black5: #aeaeae;
+$black6: #c1c1c1;
+$black7: #dcdcdc;
+$white: #ffffff;
+
+// font-family: 'M PLUS Rounded 1c', sans-serif;
+// font-family: 'Meera Inimai', sans-serif;
+// font-family: 'Roboto', sans-serif;
+// font-family: 'Signika', sans-serif;
+// font-family: 'Varela Round', sans-serif;
+
+
+
+.truncate-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+#root{
+ width: 100%;
+ padding: 0 0px;
+ display: flex;
+ height: 100vh;
+
+ .main_container{
+ &.dark_mode{
+ color:#dcdcdc
+ }
+ }
+}
+body{
+ margin:0;
+ padding:0;
+ background:$black1;
+ color:$black1;
+ font-weight: 300;
+ display:flex;
+ justify-content: center;
+ align-items: center;
+ letter-spacing: 0.04em;
+
+
+ select{
+ border-radius: 5px;
+ padding: 2px;
+ font-family: inherit;
+ background: rgba(255, 255, 255, 0.5);
+ border: 1px solid #ffffff57;
+ outline: 1px solid #00000030;
+ }
+
+ button{
+ border: none;
+ border-radius: 10px;
+ cursor:pointer;
+ padding:0 10px;
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.5);
+ border: 1px solid #ffffff57;
+ outline: 1px solid #00000030;
+ color: #000000;
+
+ @include typography(
+ $subheading-size,
+ 500,
+ $default-line-height,
+ #000000
+ );
+
+ .btn_text{
+ @include typography(
+ $subheading-size,
+ 500,
+ $default-line-height,
+ #000000
+ );
+ &.dark{
+ color:#000000
+ }
+ }
+
+ &:hover{
+ background: rgba(255, 255, 255, 0.35);
+ }
+ }
+
+ .content{
+ font-weight: 400;
+ }
+
+ a{
+ text-decoration: none;
+ color:currentColor;
+ }
+
+ input{
+ flex:1;
+ border: none;
+ border-radius: 10px;
+ font-family: inherit;
+ background: rgba(255, 255, 255, 0.5);
+ border: 1px solid #ffffff57;
+ outline: 1px solid #00000030;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ cursor:pointer;
+ @include typography(
+ $heading6-size,
+ 300,
+ unset,
+ #000000
+ );
+ padding:10px !important;
+
+ }
+
+ .truncate {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ *{
+ // font-family: 'M PLUS Rounded 1c', sans-serif;
+// font-family: 'Meera Inimai', sans-serif;
+font-family: 'Roboto', sans-serif;
+// font-family: 'Signika', sans-serif;
+// font-family: 'Varela Round', sans-serif;
+ font-weight: 400;
+ // transition: transform 0.3s 0.1s ease-in-out;
+ &.dark{
+ color:#ffffff
+ }
+ }
+
+ .main_container{
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ flex: 1 1;
+ height: 100vh;
+ justify-content: center;
+ position:relative;
+
+ .image_container{
+ width:100vw;
+ height:100vh;
+ top:0;
+ left:0;
+ position:absolute;
+ &>img{
+ width: 100vw;
+ height: 100vh;
+ object-fit: cover;
+ object-position: center center;
+ position:absolute;
+ }
+ }
+ .image_backdrop{
+ position: absolute;
+ width: 100vw;
+ height: 100vh;
+ background-color: transparent;
+ backdrop-filter: blur(21px);
+ }
+
+
+
+ .blur_filter{
+ background-color: #ffffff23;
+ width:100vw;
+ height:100vh;
+ position:absolute;
+ opacity:1;
+ backdrop-filter: blur(6px);
+ }
+
+ .main_register_container{
+ position:absolute;
+ .glassmorphic-background{
+ &>h1{
+ text-align: center;
+ }
+ }
+
+ }
+
+ .main_login_container{
+ padding:0;
+ position:absolute;
+ .glassmorphic-background{
+ &>h1{
+ text-align: center;
+ }
+ }
+ }
+
+ .parent_todo_head{
+
+ h1{
+ text-align: center;
+ }
+ .main_input_container{
+ display: flex;
+ align-items: center;
+ }
+ .main_input{
+ margin: 0 20px 0 20px !important;
+ width: 300px;
+ }
+ }
+
+ }
+ .todos_list_container{
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ margin-top: 10px;
+ }
+ input{
+ border-radius: 5px;
+ border: none;
+ font-size: 16px;
+ background: rgb(255 255 255 / 24%);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(4px);
+ width:calc(100% - 20px);
+ }
+ textarea{
+ border-radius: 10px;
+ border: none;
+ font-size: 16px;
+ background: rgb(255 255 255 / 24%);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(4px);
+ padding:0 10px;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+ width:calc(100% - 20px);
+ resize: none;
+ }
+
+ input[type="text"], input[type="password"] {
+ height: 12px;
+ padding:0 10px;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+ flex:1;
+ }
+
+input[type="text"]::placeholder,
+textarea::placeholder {
+ font-family: 'Roboto', sans-serif;
+}
+}
+
+textarea {
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ font-size: 4px;
+}
+
+.opacity0\&disable{
+ opacity:0;
+ pointer-events: none;
+}
\ No newline at end of file
diff --git a/admin-client/src/App.test.tsx b/admin-client/src/App.test.tsx
new file mode 100644
index 0000000..2a68616
--- /dev/null
+++ b/admin-client/src/App.test.tsx
@@ -0,0 +1,9 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+ render( );
+ const linkElement = screen.getByText(/learn react/i);
+ expect(linkElement).toBeInTheDocument();
+});
diff --git a/admin-client/src/App.tsx b/admin-client/src/App.tsx
new file mode 100644
index 0000000..ab5b15d
--- /dev/null
+++ b/admin-client/src/App.tsx
@@ -0,0 +1,176 @@
+import React, { useEffect, useState } from "react";
+import "./App.scss";
+import { Navigate, Route, Routes } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { RootState } from "./ReduxStore/store";
+
+import bgDark from './medias/bgfinaldark.jpg'
+import bgLight from './medias/bgfinallight.jpg'
+import LoginPage from "./Pages/Admin/LoginPage";
+import RegisterPage from "./Pages/Admin/RegisterPage";
+import Loader from "./components/UIComponents/Loader/Loader";
+import { useDispatch } from "react-redux";
+import { /*UserLogout,*/ setAllUserData, setToken } from "./ReduxStore/UserSlice";
+import DashboardWrapper from "./components/WRAPPERS/DashboardWrapper/DashboardWrapper";
+import TodosListContainer from "./components/UIComponents/Todos/TodosListContainer/TodosListContainer";
+import {/* UILogout,*/ setAllTodos, setDarkMode, setLoading } from "./ReduxStore/UISlice";
+import { getUrl, isDarkModeFromLocalStorage } from "./CONFIG";
+import TodoDetails from "./components/UIComponents/TodoDetails/TodoDetails";
+import NotFound from "./Pages/NotFound/NotFound";
+import Profile from "./components/UIComponents/Profile/Profile";
+import ForgotPassword from "./components/Admin/ForgotPassword/ForgotPassword";
+
+export interface TodoItem {
+ attachments: any[]; // You can specify the actual type for attachments if needed
+ collaborators: any[]; // You can specify the actual type for collaborators if needed
+ createdAt: string;
+ dependencies: any[]; // You can specify the actual type for dependencies if needed
+ description: string;
+ priority: string;
+ progress: number;
+ recurring: boolean;
+ status: string;
+ subtasks: any[]; // You can specify the actual type for subtasks if needed
+ tags: any[]; // You can specify the actual type for tags if needed
+ title: string;
+ todo: any[]; // You can specify the actual type for todo if needed
+ updatedAt: string;
+ user: string;
+ __v: number;
+ _id: string;
+}
+export function generateUniqueId(): string {
+ const timestamp = new Date().getTime();
+ const randomNumber = Math.floor(Math.random() * 100000);
+ return `${timestamp}_${randomNumber}`;
+}
+const App: React.FC = () => {
+
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+
+ const isLoading = useSelector((state: RootState) => state.UI.loading);
+ // Inside your component or any other place where you want to trigger the API call
+ const dispatch = useDispatch();
+
+ const allTodos = useSelector((state: RootState) => state.User.allUserData.todos)
+ // const userAllData = useSelector((state: RootState) => state.User.allUserData)
+ const token = useSelector((state: RootState) => state.User.token)
+ // const userProfile = useSelector((state: RootState) => state.User.allUserData)
+ const theme = useSelector((state: RootState) => state.UI.theme)
+
+
+
+ const fetchAllUserData = (token: string) => {
+ if (token) {
+ dispatch(setLoading(true))
+ try {
+ if (token !== null) {
+ fetch(getUrl('/auth/profile'), {
+ method: 'GET',
+ headers: {
+ 'Authorization': token
+ }
+ }).then(
+ (res) => {
+ if (res.ok) {
+ return res.json()
+ }
+ }
+ ).then((jsonData) => {
+ // if(jsonData && jsonData.user){
+ // console.log(jsonData)
+ dispatch(setAllUserData(jsonData.user))
+ dispatch(setAllTodos(jsonData.user.todos))
+ // }
+ })
+ }
+ } catch (err) {
+ console.error('Error:', err);
+ }
+ dispatch(setLoading(false))
+ } else {
+ throw new Error("Token is not present")
+ }
+ }
+ // Function to handle logout
+ const handleLogout = () => {
+ localStorage.removeItem("Token")
+ window.location.href = '/login'
+ };
+
+ useEffect(() => {
+ const localStorage_jwtToken = localStorage.getItem("Token")
+ if (!token && localStorage_jwtToken) {
+ dispatch(setToken(localStorage_jwtToken))
+ setIsAuthenticated(true)
+ } else if (token && localStorage_jwtToken) {
+ setIsAuthenticated(true)
+ } else {
+ setIsAuthenticated(false)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [token])
+
+ useEffect(() => {
+ if (token) {
+ dispatch(setLoading(true))
+ fetchAllUserData(token)
+ dispatch(setLoading(false))
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [token])
+
+ useEffect(() => {
+ // dispatch(setLoading(true))
+ const darkMode = isDarkModeFromLocalStorage()
+ if (darkMode) {
+ dispatch(setDarkMode(true))
+ } else {
+ dispatch(setDarkMode(false))
+ }
+ // dispatch(setLoading(false))
+ }, [dispatch, theme.dark])
+
+ return (
+
+ {theme.dark ?
:
}
+ {/*
*/}
+
+
+ {!isAuthenticated && (
+ <>
+ } />
+ } />
+ } />
+ } />
+ >
+ )}
+
+ {isAuthenticated ? (<>
+ {/* } /> */}
+
+ }>
+ {/* Nested routes for the dashboard */}
+ {/* } /> */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Fallback route for any other unmatched paths */}
+ } />
+ >
+ ) : <>>}
+ {/* Fallback route for other unmatched paths */}
+ } />
+
+
+
+
+ );
+};
+
+export default App;
diff --git a/admin-client/src/CONFIG/index.ts b/admin-client/src/CONFIG/index.ts
new file mode 100644
index 0000000..2a8f446
--- /dev/null
+++ b/admin-client/src/CONFIG/index.ts
@@ -0,0 +1,96 @@
+import { API_URL_LIVE, API_URL_LOCAL, isLive } from "../api";
+
+export const getUrl =(remUrl:string) => `${isLive ? API_URL_LIVE : API_URL_LOCAL}${remUrl}`
+
+export function formatDateAndTime(dateObj: Date): [string,string] {
+ const day = dateObj.getDate();
+ const month = dateObj.toLocaleString('default', { month: 'short' });
+ const year = dateObj.getFullYear().toString().slice(-2);
+
+ let daySuffix;
+ if (day === 1 || day === 21 || day === 31) {
+ daySuffix = 'st';
+ } else if (day === 2 || day === 22) {
+ daySuffix = 'nd';
+ } else if (day === 3 || day === 23) {
+ daySuffix = 'rd';
+ } else {
+ daySuffix = 'th';
+ }
+
+ const hours = dateObj.getHours();
+ const minutes = dateObj.getMinutes();
+ const timeOfDay = hours >= 12 ? 'PM' : 'AM';
+ const formattedHours = (hours % 12) || 12; // Convert to 12-hour format
+
+ const formattedTime = `${formattedHours}:${minutes.toString().padStart(2, '0')} ${timeOfDay}`;
+
+ // return `${day}${daySuffix} ${month} ${year} - ${formattedTime}`;
+ return [`${day}${daySuffix} ${month} ${year}`,`${formattedTime}`]
+
+}
+
+export const isDarkModeFromLocalStorage = () => {
+ const localStorageDarkMode = localStorage.getItem('darkMode')
+
+ if (localStorageDarkMode != null && localStorageDarkMode === 'True') {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+export const includeDarkClass =(scssClass:string,darkMode:boolean)=>{
+ return `${scssClass} ${darkMode?'dark':'light'}`
+}
+const Todos = {
+ getUrl:getUrl,
+ formatDateAndTime:formatDateAndTime
+}
+
+
+type HttpError = {
+ message: string;
+ status?: number;
+};
+interface HttpRequest {
+ url: string;
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
+ headers?: { [key: string]: string };
+ body?: FormData | string | null;
+}
+export const doFetchCall=async(options:HttpRequest)=>{
+ try {
+ const headers: HeadersInit = {};
+ if (options && options.headers) {
+ for (const [key, value] of Object.entries(options.headers)) {
+ if (value !== null) {
+ headers[key] = value && value.toString();
+ }
+ }
+ }
+ const fetchOptions: RequestInit = {
+ ...options,
+ headers,
+ };
+
+ const fetchResponse = await fetch(options.url, fetchOptions);
+ if (!fetchResponse.ok) {
+ // dispatch(setLoading(false));
+ throw new Error(`Request failed with status: ${fetchResponse.status}`);
+ }
+
+ const responseData = await fetchResponse.json();
+ return responseData
+ // dispatch(setLoading(false));
+ } catch (error: unknown) {
+ return{
+ message: (error as Error).message,
+ status: (error as HttpError).status,
+ };
+ // dispatch(setLoading(false));
+ }
+}
+
+export default Todos;
\ No newline at end of file
diff --git a/admin-client/src/Pages/Admin/LoginPage.tsx b/admin-client/src/Pages/Admin/LoginPage.tsx
new file mode 100644
index 0000000..208f0d7
--- /dev/null
+++ b/admin-client/src/Pages/Admin/LoginPage.tsx
@@ -0,0 +1,26 @@
+import { useEffect } from "react";
+import Login from "../../components/Admin/Login/Login"
+import { useNavigate } from "react-router-dom";
+// import { useSelector } from "react-redux";
+// import { RootState } from "../../ReduxStore/store";
+
+export interface LoginPageProps {
+ setIsAuthenticated: any;
+ isAuthenticated: boolean;
+ fetchAllUserData: any;
+}
+const LoginPage: React.FC = ({ setIsAuthenticated, isAuthenticated, fetchAllUserData = () => { } }) => {
+ const navigate = useNavigate()
+ // const reduxToken = useSelector((state: RootState) => state.User.token)
+ useEffect(() => {
+ if (isAuthenticated) {
+ navigate('/dashboard')
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+ return(
+
+ )
+}
+
+export default LoginPage;
\ No newline at end of file
diff --git a/admin-client/src/Pages/Admin/RegisterPage.tsx b/admin-client/src/Pages/Admin/RegisterPage.tsx
new file mode 100644
index 0000000..2c2d707
--- /dev/null
+++ b/admin-client/src/Pages/Admin/RegisterPage.tsx
@@ -0,0 +1,26 @@
+// import React, { useEffect } from 'react'
+import Register from '../../components/Admin/Register/Register'
+// import { useNavigate } from 'react-router-dom';
+
+interface RegisterPageProps {
+ setIsAuthenticated: any;
+}
+
+const RegisterPage: React.FC = ({ setIsAuthenticated }) => {
+ // const navigate = useNavigate()
+ // useEffect(() => {
+ // const token = localStorage.getItem('Token');
+ // if (!token) {
+ // setIsAuthenticated(false)
+ // } else {
+ // setIsAuthenticated(true)
+ // navigate('/dashboard')
+ // }
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, []);
+ return (
+
+ )
+}
+
+export default RegisterPage
\ No newline at end of file
diff --git a/admin-client/src/Pages/NotFound/NotFound.scss b/admin-client/src/Pages/NotFound/NotFound.scss
new file mode 100644
index 0000000..b721066
--- /dev/null
+++ b/admin-client/src/Pages/NotFound/NotFound.scss
@@ -0,0 +1,9 @@
+.notfound_main_container{
+ position:absolute;
+ width: 100%;
+ height: 100%;
+ backdrop-filter: blur(50px);
+}
+.notfound_main_container.isAuthenticated{
+ background-color: aliceblue;
+}
\ No newline at end of file
diff --git a/admin-client/src/Pages/NotFound/NotFound.tsx b/admin-client/src/Pages/NotFound/NotFound.tsx
new file mode 100644
index 0000000..fde6e5d
--- /dev/null
+++ b/admin-client/src/Pages/NotFound/NotFound.tsx
@@ -0,0 +1,40 @@
+import { useSelector } from "react-redux";
+import { includeDarkClass } from "../../CONFIG";
+import "./NotFound.scss";
+import { RootState } from "../../ReduxStore/store";
+import { useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { useDispatch } from "react-redux";
+import { setLoading } from "../../ReduxStore/UISlice";
+
+interface NotFoundProps {
+ isAuthenticated: boolean
+}
+
+const NotFound: React.FC = ({ isAuthenticated = false }) => {
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+ const token = useSelector((state: RootState) => state.User.token)
+ const navigate = useNavigate()
+ const dispatch = useDispatch()
+ useEffect(() => {
+ dispatch(setLoading(true))
+ if (token) {
+ navigate('/todos')
+ }
+ dispatch(setLoading(false))
+ }, [dispatch, navigate, token])
+ if (isAuthenticated) {
+ return (
+
+
NOT FOUNDsdfsdf
+
+ )
+ }
+ return (
+
+
NOT FOUND
+
+ )
+}
+
+export default NotFound;
\ No newline at end of file
diff --git a/admin-client/src/ReduxStore/UISlice.ts b/admin-client/src/ReduxStore/UISlice.ts
new file mode 100644
index 0000000..671b77d
--- /dev/null
+++ b/admin-client/src/ReduxStore/UISlice.ts
@@ -0,0 +1,80 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+interface UISliceReducerState {
+ allTodos:any;
+ data: string[];
+ loading: boolean;
+ token:string | null;
+ sideBarActiveTab:number;
+ theme:{
+ dark:boolean
+ },
+ currentPage:string;
+ isMobSidebarOpen:boolean;
+}
+
+const initialState: UISliceReducerState = {
+ allTodos:null,
+ data: [],
+ loading: false,
+ token:null,
+ sideBarActiveTab:0,
+ theme:{
+ dark:false
+ },
+ currentPage:'',
+ isMobSidebarOpen:false
+};
+
+const UISliceReducer = createSlice({
+ name: 'UI',
+ initialState,
+ reducers: {
+ setToken(state,action:PayloadAction) {
+ state.token = action.payload;
+ let isTokenPresent = localStorage.getItem('Token');
+ if(!isTokenPresent){
+ localStorage.setItem('Token',action.payload);
+ }else{
+ localStorage.removeItem('Token');
+ localStorage.setItem('Token',action.payload);
+ }
+ },
+ setLoading(state, action: PayloadAction) {
+ state.loading = action.payload;
+ },
+ setAllTodos(state,action:PayloadAction){
+ state.allTodos = action.payload;
+ },
+ UILogout(state){
+ state.allTodos = [];
+ state.data = [];
+ state.loading = false;
+ state.sideBarActiveTab = 0;
+ state.token = null;
+ },
+ toogleDarkLight(state){
+ if(state.theme.dark){
+ localStorage.setItem('darkMode','False')
+ }else{
+ localStorage.setItem('darkMode','True')
+ }
+ state.theme.dark = !state.theme.dark;
+ },
+ setDarkMode(state,action:PayloadAction){
+ state.theme.dark = action.payload;
+ },
+ setCurrentPage(state,action:PayloadAction){
+ state.currentPage = action.payload;
+ },
+ toggleMobSidebar(state){
+ state.isMobSidebarOpen = !state.isMobSidebarOpen;
+ },
+ setSideBarActiveTab(state,action:PayloadAction){
+ state.sideBarActiveTab = action.payload
+ }
+ },
+});
+
+export const { setToken, setLoading,setAllTodos ,UILogout,toogleDarkLight,setDarkMode,setCurrentPage,toggleMobSidebar,setSideBarActiveTab} = UISliceReducer.actions;
+export default UISliceReducer.reducer;
diff --git a/admin-client/src/ReduxStore/UserSlice.ts b/admin-client/src/ReduxStore/UserSlice.ts
new file mode 100644
index 0000000..08aef96
--- /dev/null
+++ b/admin-client/src/ReduxStore/UserSlice.ts
@@ -0,0 +1,82 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { TodoItem } from '../App';
+
+export interface Todo {
+ _id: string;
+ title: string;
+ user: string;
+ description: string;
+ todo: Todo[];
+ createdAt: string;
+ updatedAt: string;
+ __v: number;
+}
+
+export interface User {
+ _id: string;
+ userName: string;
+ password: string;
+ email: string;
+ picUrl: string;
+ todos: TodoItem[];
+ social: {};
+ statusFiltered: {
+ __filteredTodos: TodoItem[];
+ __filteredInProgress: TodoItem[];
+ __filteredCompleted: TodoItem[];
+ __filteredOnHold: TodoItem[];
+ };
+ priorityFiltered: {
+ __filteredHigh: TodoItem[];
+ __filteredMedium: TodoItem[];
+ __filteredLow: TodoItem[];
+ };
+ createdAt: string;
+ updatedAt: string;
+ website: string;
+ bio: string;
+ location: string;
+ __v: number;
+}
+
+// export interface ApiResponse {
+// user: User;
+// }
+
+interface UISliceReducerState {
+ allUserData: Partial;
+ token: string | null;
+}
+
+const initialState: UISliceReducerState = {
+ allUserData: {},
+ token: null,
+};
+
+const UserSliceReducer = createSlice({
+ name: 'User',
+ initialState,
+ reducers: {
+ setAllUserData(state, action: PayloadAction) {
+ state.allUserData = action.payload;
+ },
+ setToken(state, action: PayloadAction) {
+ state.token = action.payload
+ let isTokenPresent = localStorage.getItem('Token');
+ if (!isTokenPresent) {
+ localStorage.setItem('Token', action.payload)
+ } else {
+ localStorage.removeItem('Token')
+ localStorage.setItem('Token', action.payload)
+ }
+ },
+ UserLogout(state) {
+ state.token = null;
+ state.allUserData = {};
+ },
+ //
+ },
+});
+
+export const { setAllUserData, setToken, UserLogout } = UserSliceReducer.actions;
+export default UserSliceReducer.reducer;
diff --git a/admin-client/src/ReduxStore/store.ts b/admin-client/src/ReduxStore/store.ts
new file mode 100644
index 0000000..0f171d7
--- /dev/null
+++ b/admin-client/src/ReduxStore/store.ts
@@ -0,0 +1,19 @@
+// store.ts
+import { configureStore } from '@reduxjs/toolkit';
+import UISliceReducer from './UISlice';
+import UserSliceReducer from './UserSlice';
+// import slice2Reducer from './slice2';
+// import slice3Reducer from './slice3';
+
+const store = configureStore({
+ reducer: {
+ UI: UISliceReducer,
+ User: UserSliceReducer,
+ // slice3: slice3Reducer,
+ },
+});
+
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
+
+export default store;
diff --git a/admin-client/src/api.ts b/admin-client/src/api.ts
new file mode 100644
index 0000000..3976668
--- /dev/null
+++ b/admin-client/src/api.ts
@@ -0,0 +1,32 @@
+
+// local one
+
+// export const API_URL_LOCAL = "http://192.168.0.101:3033"
+// export const API_URL_LOCAL = "http://192.168.0.103:3033"
+// export const API_URL_LOCAL = "http://192.168.68.63:3033/jarvis"
+export const API_URL_LOCAL = "http://192.168.0.100:3033/jarvis"
+
+// personal host
+// export const API_URL_LOCAL = "http://172.20.10.13:3033"
+
+// live one
+// export const API_URL_LIVE = "https://adnans-todo-backend.onrender.com/jarvis"
+// export const API_URL_LIVE = "http://ec2-16-170-250-205.eu-north-1.compute.amazonaws.com:3033/jarvis"
+export const API_URL_LIVE = "https://backend.3621.lol/jarvis"
+
+// isLive
+export const isLive = true;
+
+
+export function generateUniqueID() {
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ const length = 32;
+ let id = '';
+
+ for (let i = 0; i < length; i++) {
+ const randomIndex = Math.floor(Math.random() * characters.length);
+ id += characters.charAt(randomIndex);
+ }
+
+ return id;
+}
\ No newline at end of file
diff --git a/admin-client/src/components/Admin/ForgotPassword/ForgotPassword.scss b/admin-client/src/components/Admin/ForgotPassword/ForgotPassword.scss
new file mode 100644
index 0000000..e69de29
diff --git a/admin-client/src/components/Admin/ForgotPassword/ForgotPassword.tsx b/admin-client/src/components/Admin/ForgotPassword/ForgotPassword.tsx
new file mode 100644
index 0000000..4725814
--- /dev/null
+++ b/admin-client/src/components/Admin/ForgotPassword/ForgotPassword.tsx
@@ -0,0 +1,162 @@
+import React, { /*useEffect,*/ useState } from 'react';
+import { LoginPageProps } from '../../../Pages/Admin/LoginPage';
+import PasswordInput from '../../UIComponents/PasswordInput';
+import "./ForgotPassword.scss"
+import GlassmorphicBackground from '../../UIComponents/Modal/DesignComponents/GlassmorphicBackground';
+// import Loader from '../../UIComponents/Loader/Loader';
+import { Link, useNavigate } from 'react-router-dom';
+import { setLoading } from '../../../ReduxStore/UISlice';
+import { useDispatch } from 'react-redux';
+import { getUrl, includeDarkClass } from '../../../CONFIG';
+import { RootState } from '../../../ReduxStore/store';
+import { useSelector } from 'react-redux';
+import { setToken } from '../../../ReduxStore/UserSlice';
+
+interface ForgotPasswordProps extends LoginPageProps {
+ setIsAuthenticated: any;
+}
+
+const ForgotPassword: React.FC = ({ setIsAuthenticated, fetchAllUserData }) => {
+ const [email, setEmail] = useState('');
+ const [OTP, setOTP] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState(null);
+ const [OTPSent, setOTPSent] = useState(false)
+
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+
+ const navigate = useNavigate()
+
+ const dispatch = useDispatch()
+ const token = useSelector((state: RootState) => state.User.token)
+
+ const handlEmailChange = (event: React.ChangeEvent) => {
+ setEmail(event.target.value);
+ };
+
+ const handleOTPChange = (event: React.ChangeEvent) => {
+ setOTP(event.target.value);
+ };
+
+ const handlePasswordChange = (event: React.ChangeEvent) => {
+ setPassword(event.target.value);
+ };
+
+
+ const handleOTPFormSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ dispatch(setLoading(true))
+
+ const formdata = new FormData();
+ formdata.append('email', email);
+ // formdata.append('OTP', OTP);
+
+ fetch(getUrl('/auth/forgotPassword'), {
+ method: 'POST',
+ body: formdata,
+ }).then((response) => {
+ if (response.status === 200 || response.ok) {
+ setOTPSent(true)
+ console.log('OTP sent successfully !!.')
+ }
+ return response.json()
+ }).then((jsonResponse) => {
+ dispatch(setLoading(false))
+ setError(jsonResponse && jsonResponse.message)
+ if (jsonResponse && jsonResponse.token) {
+ localStorage.setItem("Token", jsonResponse && jsonResponse.token)
+ dispatch(setToken(jsonResponse.token))
+ // dispatch(setLoading(true))
+ // fetchAllUserData(jsonResponse.token)
+ // dispatch(setLoading(false))
+ if (token) {
+ navigate('/todos')
+ }
+ }
+ })
+ .catch(err => {
+ console.error(err)
+ setError(err)
+ dispatch(setLoading(false))
+ })
+ };
+ const handleResetPasswordFormSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ dispatch(setLoading(true))
+
+ const formdata = new FormData();
+ formdata.append('email', email);
+ formdata.append('newPassword', password);
+ formdata.append('OTP', OTP);
+
+ fetch(getUrl('/auth/resetPassword'), {
+ method: 'POST',
+ body: formdata,
+ }).then((response) => {
+ if (response.status === 200 || response.ok) {
+ // setOTPSent(true)
+ console.log('Password resetted successfully !!.')
+ navigate('/login')
+ }
+ return response.json()
+ }).then((jsonResponse) => {
+ dispatch(setLoading(false))
+ setError(jsonResponse && jsonResponse.message)
+ if (jsonResponse && jsonResponse.token) {
+ localStorage.setItem("Token", jsonResponse && jsonResponse.token)
+ dispatch(setToken(jsonResponse.token))
+ // dispatch(setLoading(true))
+ // fetchAllUserData(jsonResponse.token)
+ // dispatch(setLoading(false))
+ if (token) {
+ navigate('/todos')
+ }
+ }
+ })
+ .catch(err => {
+ console.error(err)
+ setError(err)
+ dispatch(setLoading(false))
+ })
+ };
+
+ return (
+
+
+ Forgot Password
+
+ {error && {JSON.stringify(error)}
}
+
+ Don't have an account ?
+ Sign Up / Register
+
+
+
+ );
+};
+
+export default ForgotPassword;
diff --git a/admin-client/src/components/Admin/Login/Login.scss b/admin-client/src/components/Admin/Login/Login.scss
new file mode 100644
index 0000000..2010749
--- /dev/null
+++ b/admin-client/src/components/Admin/Login/Login.scss
@@ -0,0 +1,46 @@
+.main_login_container{
+ width: 400px;
+ padding: 35px;
+
+ h2{
+ margin-top: 0;
+ }
+
+ .glassmorphic-background{
+ padding:20px 30px
+ }
+}
+
+.login__form{
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ row-gap: 20px;
+
+ div.inputdiv{
+ input{
+ width:93%;
+ margin-top: 6px;
+ }
+ }
+}
+
+.login__btn{
+ background-color: #e7f0ff;
+ border-radius: 5px;
+ font-family: 'Signika', sans-serif;
+ font-size: 20px;
+ // width:50px;
+
+ &.dark{
+ color:black
+ }
+}
+
+.sign_in_redirect{
+ margin: 10px 0;
+
+ span{
+ color:rgb(28, 167, 253)
+ }
+}
\ No newline at end of file
diff --git a/admin-client/src/components/Admin/Login/Login.tsx b/admin-client/src/components/Admin/Login/Login.tsx
new file mode 100644
index 0000000..3f3922a
--- /dev/null
+++ b/admin-client/src/components/Admin/Login/Login.tsx
@@ -0,0 +1,105 @@
+import React, { /*useEffect,*/ useState } from 'react';
+import { LoginPageProps } from '../../../Pages/Admin/LoginPage';
+import PasswordInput from '../../UIComponents/PasswordInput';
+import "./Login.scss"
+import GlassmorphicBackground from '../../UIComponents/Modal/DesignComponents/GlassmorphicBackground';
+// import Loader from '../../UIComponents/Loader/Loader';
+import { Link, useNavigate } from 'react-router-dom';
+import { setLoading } from '../../../ReduxStore/UISlice';
+import { useDispatch } from 'react-redux';
+import { getUrl, includeDarkClass } from '../../../CONFIG';
+import { RootState } from '../../../ReduxStore/store';
+import { useSelector } from 'react-redux';
+import { setToken } from '../../../ReduxStore/UserSlice';
+
+interface LoginProps extends LoginPageProps{
+ setIsAuthenticated: any;
+}
+
+const Login: React.FC = ({ setIsAuthenticated, fetchAllUserData }) => {
+ const [userName, setUserName] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState(null);
+
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+
+ const navigate = useNavigate()
+
+ const dispatch = useDispatch()
+ const token = useSelector((state: RootState) => state.User.token)
+
+ const handleUsernameChange = (event: React.ChangeEvent) => {
+ setUserName(event.target.value);
+ };
+
+ const handlePasswordChange = (event: React.ChangeEvent) => {
+ setPassword(event.target.value);
+ };
+
+
+ const handleLoginFormSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ dispatch(setLoading(true))
+
+ const formdata = new FormData();
+ formdata.append('userName', userName);
+ formdata.append('password', password);
+
+ fetch(getUrl('/auth/login'), {
+ method: 'POST',
+ body: formdata,
+ }).then((response) => {
+ if (response.status === 200 || response.ok) {
+ console.log("user logged in")
+ }
+ return response.json()
+ }).then((jsonResponse) => {
+ dispatch(setLoading(false))
+ setError(jsonResponse && jsonResponse.message)
+ if (jsonResponse && jsonResponse.token) {
+ localStorage.setItem("Token", jsonResponse && jsonResponse.token)
+ dispatch(setToken(jsonResponse.token))
+ // dispatch(setLoading(true))
+ // fetchAllUserData(jsonResponse.token)
+ // dispatch(setLoading(false))
+ if (token) {
+ navigate('/todos')
+ }
+ }
+ })
+ .catch(err => {
+ console.error(err)
+ setError(err)
+ dispatch(setLoading(false))
+ })
+ };
+
+ return (
+
+
+ Login
+
+ {error && {JSON.stringify(error)}
}
+
+ Don't have an account ?
+ Sign Up / Register
+
+
+ Forgot Password ??
+
+
+
+ );
+};
+
+export default Login;
diff --git a/admin-client/src/components/Admin/Register/Register.scss b/admin-client/src/components/Admin/Register/Register.scss
new file mode 100644
index 0000000..88fe6f7
--- /dev/null
+++ b/admin-client/src/components/Admin/Register/Register.scss
@@ -0,0 +1,17 @@
+.main_register_container{
+ width: 400px;
+
+ .sign_in_redirect{
+ width:100%;
+
+ a{
+ text-decoration: none;
+ }
+
+ span{
+ color:rgb(28, 167, 253);
+ font-weight: 500;
+ padding-left: 10px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/admin-client/src/components/Admin/Register/Register.tsx b/admin-client/src/components/Admin/Register/Register.tsx
new file mode 100644
index 0000000..23a5c4c
--- /dev/null
+++ b/admin-client/src/components/Admin/Register/Register.tsx
@@ -0,0 +1,93 @@
+import React, { useState } from 'react'
+import "./Register.scss"
+import { Link, useNavigate } from 'react-router-dom';
+import GlassmorphicBackground from '../../UIComponents/Modal/DesignComponents/GlassmorphicBackground';
+import PasswordInput from '../../UIComponents/PasswordInput';
+import { setLoading } from '../../../ReduxStore/UISlice';
+import { useDispatch } from 'react-redux';
+import { getUrl } from '../../../CONFIG';
+
+
+interface RegisterProps {
+}
+
+const Register: React.FC = () => {
+ const [userName, setUserName] = useState('');
+ const [password, setPassword] = useState('');
+ const [email, setEmail] = useState('');
+ const [profilePicUrl, setProfilePicUrl] = useState('');
+
+ const navigate = useNavigate()
+
+ const dispatch = useDispatch()
+
+ const handleUserNameChange = (event: React.ChangeEvent) => {
+ setUserName(event.target.value);
+ };
+
+ const handlePasswordChange = (event: React.ChangeEvent) => {
+ setPassword(event.target.value);
+ };
+ const handleEmailChange = (event: React.ChangeEvent) => {
+ setEmail(event.target.value);
+ };
+ const handleProfilePicUrlChange = (event: React.ChangeEvent) => {
+ setProfilePicUrl(event.target.value);
+ };
+
+ const handleLoginFormSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+ dispatch(setLoading(true))
+ const formdata = new FormData();
+ formdata.append('userName', userName);
+ formdata.append('email', email);
+ formdata.append('password', password);
+ formdata.append('profilePicUrl', profilePicUrl);
+
+ fetch(getUrl('/auth/register'), {
+ method: 'POST',
+ body: formdata,
+ }).then((response) => {
+ if (response.ok) {
+ dispatch(setLoading(false))
+ navigate('/login')
+ }
+ dispatch(setLoading(false))
+ }).catch(err => {
+ console.error(err)
+ dispatch(setLoading(false))
+ })
+ };
+
+ return (
+
+ )
+}
+
+export default Register
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/AddIcon/AddIcon.scss b/admin-client/src/components/UIComponents/AddIcon/AddIcon.scss
new file mode 100644
index 0000000..4bb5daa
--- /dev/null
+++ b/admin-client/src/components/UIComponents/AddIcon/AddIcon.scss
@@ -0,0 +1,73 @@
+@import '../../../styles/typography/typography.scss';
+
+// AddIcon component styles
+.add_icon {
+ position: relative;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ // transition: background-color 0.2s ease-in-out;
+ border: 2px solid rgba(255, 255, 255, 0.24);
+
+
+ // Default size
+ width: 25px;
+ height: 25px;
+
+ // Optional size passed as a prop
+
+ &.large {
+ width: 40px;
+ height: 40px;
+ }
+
+ // Optional background color passed as a prop
+ &.custom-background {
+ background-color: lightblue;
+ }
+
+ &.darkMode{
+ border: 3px solid $link_blue;
+ }
+
+ .line1, .line2 {
+ width: 70%;
+ height: 4px;
+ background: rgba(255, 255, 255, 0.68);
+ backdrop-filter: blur(4px);
+ border-radius: 10px;
+
+ &.darkMode{
+ background: $link_blue;
+ }
+ }
+
+ .line2 {
+ position: absolute;
+ transform: rotate(90deg);
+ }
+
+ #tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(0, 0, 0, 0.8);
+ color: #fff;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ // transition: opacity 0.2s ease-in-out;
+ }
+
+
+ &:hover #tooltip {
+ opacity: 1;
+ }
+ }
+
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/AddIcon/AddIcon.tsx b/admin-client/src/components/UIComponents/AddIcon/AddIcon.tsx
new file mode 100644
index 0000000..a455af0
--- /dev/null
+++ b/admin-client/src/components/UIComponents/AddIcon/AddIcon.tsx
@@ -0,0 +1,82 @@
+import React, { FC, CSSProperties } from 'react';
+import './AddIcon.scss'
+
+interface AddIconProps {
+ onClick?: Function;
+ darkMode?: boolean;
+ backgroundColor?: string;
+ size?: number;
+ tooltipText?: string;
+ showToolTip: boolean;
+}
+
+const AddIcon: FC = ({
+ backgroundColor = 'none',
+ size = 25,
+ tooltipText = 'Add',
+ darkMode = false,
+ showToolTip = false,
+}) => {
+ const iconStyle: CSSProperties = {
+ width: size,
+ height: size,
+ borderRadius: '50%',
+ background: backgroundColor,
+ backdropFilter: 'blur(4px)',
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ };
+
+ const tooltipStyle: CSSProperties = {
+ position: 'absolute',
+ bottom: '-112%',
+ left: '50%',
+ transform: 'translateX(-50%)',
+ background: 'rgba(0, 0, 0, 0.8)',
+ color: '#fff',
+ padding: '4px 8px',
+ borderRadius: '4px',
+ fontSize: '12px',
+ fontWeight: '400',
+ whiteSpace: 'nowrap',
+ pointerEvents: 'none',
+ opacity: 0,
+ // transition: 'opacity 0.2s ease-in-out',
+ };
+
+ const handleMouseEnter = () => {
+ const tooltip = document.getElementById('tooltip');
+ if (tooltip) {
+ tooltip.style.opacity = '1';
+ }
+ };
+
+ const handleMouseLeave = () => {
+ const tooltip = document.getElementById('tooltip');
+ if (tooltip) {
+ tooltip.style.opacity = '0';
+ }
+ };
+
+ return (
+
+
+
+ {showToolTip ?
+
+ {tooltipText}
+
: <>>
+ }
+
+
+ );
+};
+
+export default AddIcon;
diff --git a/admin-client/src/components/UIComponents/Chevron/ChevronIcon.scss b/admin-client/src/components/UIComponents/Chevron/ChevronIcon.scss
new file mode 100644
index 0000000..9dacce5
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Chevron/ChevronIcon.scss
@@ -0,0 +1,82 @@
+@import '../../../styles/typography/typography.scss';
+
+// AddIcon component styles
+.chevron_icon {
+ position: relative;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ // transition: background-color 0.2s ease-in-out;
+ border: 2px solid rgba(255, 255, 255, 0.24);
+
+
+ // Default size
+ width: 25px;
+ height: 25px;
+
+ // Optional size passed as a prop
+
+ &.large {
+ width: 40px;
+ height: 40px;
+ }
+
+ // Optional background color passed as a prop
+ &.custom-background {
+ background-color: lightblue;
+ }
+
+ &.darkMode{
+ border: 3px solid $link_blue;
+ }
+
+ .line1, .line2 {
+ width: 50%;
+ height: 4px;
+ background: rgba(255, 255, 255, 0.68);
+ backdrop-filter: blur(4px);
+ border-radius: 10px;
+
+ &.darkMode{
+ background: $link_blue;
+ }
+ }
+
+ .line2 {
+ position: absolute;
+ transform-origin: right center;
+ transform: rotate(-39deg);
+ top: 10px;
+ }
+ .line1 {
+ position: absolute;
+ transform-origin: right center;
+ transform: rotate(39deg);
+ top: 10.5px;
+
+ }
+
+ #chev_tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(0, 0, 0, 0.8);
+ color: #fff;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ // transition: opacity 0.2s ease-in-out;
+ }
+
+
+ &:hover #chev_tooltip {
+ opacity: 1;
+ }
+ }
+
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/Chevron/ChevronIcon.tsx b/admin-client/src/components/UIComponents/Chevron/ChevronIcon.tsx
new file mode 100644
index 0000000..5d35ab1
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Chevron/ChevronIcon.tsx
@@ -0,0 +1,96 @@
+import React, { FC, CSSProperties, MouseEventHandler } from 'react';
+import './ChevronIcon.scss'
+import { generateUniqueID } from '../../../api';
+
+interface ChevronIconProps {
+ onClick?: MouseEventHandler;
+ darkMode?: boolean;
+ backgroundColor?: string;
+ size?: number;
+ tooltipText?: string;
+}
+
+const ChevronIcon: FC = ({
+ backgroundColor = 'none',
+ size = 25,
+ tooltipText = 'More',
+ darkMode = false,
+ onClick = () => { }
+}) => {
+ const iconStyle: CSSProperties = {
+ width: size,
+ height: size,
+ borderRadius: '50%',
+ background: backgroundColor,
+ backdropFilter: 'blur(4px)',
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ };
+
+ const chevRandomUnique32Id = generateUniqueID()
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const tooltipStyle: CSSProperties = {
+ position: 'absolute',
+ bottom: '-112%',
+ left: '50%',
+ transform: 'translateX(-50%)',
+ background: 'rgba(0, 0, 0, 0.8)',
+ color: '#fff',
+ padding: '4px 8px',
+ borderRadius: '4px',
+ fontSize: '12px',
+ fontWeight: '400',
+ whiteSpace: 'nowrap',
+ pointerEvents: 'none',
+ opacity: 0,
+ // transition: 'opacity 0.2s ease-in-out',
+ };
+
+ const handleMouseEnter = () => {
+ const tooltip = document.getElementById(chevRandomUnique32Id);
+ if (tooltip) {
+ tooltip.style.opacity = '1';
+ }
+ };
+
+
+
+ const handleMouseLeave = () => {
+ const tooltip = document.getElementById(chevRandomUnique32Id);
+ if (tooltip) {
+ tooltip.style.opacity = '0';
+ }
+ };
+
+ // const Tooltip = ({ tooltipText = '' }) => {
+ // return (
+ // <>
+ // {ReactDOM.createPortal(
+ // {tooltipText}
+ //
, document.body)}
+ // >
+ // )
+ // }
+
+ return (
+
+
+
+ {/*
+ {tooltipText}
+
*/}
+
+ );
+};
+
+export default ChevronIcon;
diff --git a/admin-client/src/components/UIComponents/CrossIcon/CrossIcon.scss b/admin-client/src/components/UIComponents/CrossIcon/CrossIcon.scss
new file mode 100644
index 0000000..df90f1c
--- /dev/null
+++ b/admin-client/src/components/UIComponents/CrossIcon/CrossIcon.scss
@@ -0,0 +1,86 @@
+@import '../../../styles/typography/typography.scss';
+
+// AddIcon component styles
+.cross_icon {
+ position: relative;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ // transition: background-color 0.2s ease-in-out;
+ border: 2px solid rgb(255, 0, 0);
+ opacity:0.4;
+
+
+ // Default size
+ width: 25px;
+ height: 25px;
+
+ // Optional size passed as a prop
+
+ &.large {
+ width: 40px;
+ height: 40px;
+ }
+
+ // Optional background color passed as a prop
+ &.custom-background {
+ background-color: lightblue;
+ }
+
+ &.darkMode{
+ border: 3px solid $link_blue;
+ }
+
+ .line1, .line2 {
+ width: 70%;
+ height: 4px;
+ background: rgba(255, 0, 0, 0.68);
+ backdrop-filter: blur(4px);
+ border-radius: 10px;
+
+ &.darkMode{
+ background: $link_blue;
+ }
+
+ &.danger{
+ background: rgba(231, 70, 70);
+ }
+ }
+
+ .line1 {
+ position: absolute;
+ transform: rotate(45deg);
+ }
+ .line2 {
+ position: absolute;
+ transform: rotate(-45deg);
+ }
+
+ #cross_tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(0, 0, 0, 0.8);
+ color: #fff;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ // transition: opacity 0.2s ease-in-out;
+ }
+
+
+ &:hover #cross_tooltip {
+ opacity: 1;
+ }
+
+ &:hover{
+ opacity:0.7
+ }
+ }
+
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/CrossIcon/CrossIcon.tsx b/admin-client/src/components/UIComponents/CrossIcon/CrossIcon.tsx
new file mode 100644
index 0000000..ca80684
--- /dev/null
+++ b/admin-client/src/components/UIComponents/CrossIcon/CrossIcon.tsx
@@ -0,0 +1,85 @@
+import React, { FC, CSSProperties } from 'react';
+import './CrossIcon.scss'
+
+interface CrossIconProps {
+ onClick: (event: React.MouseEvent) => void;
+ darkMode?: boolean;
+ backgroundColor?: string;
+ size?: number;
+ tooltipText?: string;
+ showToolTip?: boolean;
+}
+
+const CrossIcon: FC = ({
+ backgroundColor = 'none',
+ size = 25,
+ tooltipText = 'Cross',
+ darkMode = false,
+ showToolTip = false,
+ onClick = () => { },
+}) => {
+ const iconStyle: CSSProperties = {
+ width: size,
+ height: size,
+ borderRadius: '50%',
+ background: backgroundColor,
+ backdropFilter: 'blur(4px)',
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ };
+
+ const tooltipStyle: CSSProperties = {
+ position: 'absolute',
+ bottom: '-112%',
+ left: '50%',
+ transform: 'translateX(-50%)',
+ background: 'rgba(0, 0, 0, 0.8)',
+ color: '#fff',
+ padding: '4px 8px',
+ borderRadius: '4px',
+ fontSize: '12px',
+ fontWeight: '400',
+ whiteSpace: 'nowrap',
+ pointerEvents: 'none',
+ opacity: 0,
+ // transition: 'opacity 0.2s ease-in-out',
+ };
+
+ const handleMouseEnter = () => {
+ const tooltip = document.getElementById('cross_tooltip');
+ if (tooltip) {
+ tooltip.style.opacity = '1';
+ }
+ };
+
+ const handleMouseLeave = () => {
+ const tooltip = document.getElementById('cross_tooltip');
+ if (tooltip) {
+ tooltip.style.opacity = '0';
+ }
+ };
+
+ return (
+
+
+
+ {showToolTip ?
+
+ {tooltipText}
+
: <>>
+ }
+
+
+ );
+};
+
+export default CrossIcon;
diff --git a/admin-client/src/components/UIComponents/Loader/Loader.scss b/admin-client/src/components/UIComponents/Loader/Loader.scss
new file mode 100644
index 0000000..89dbd7a
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Loader/Loader.scss
@@ -0,0 +1,45 @@
+/* Loader.css */
+.loader-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index:999999;
+ }
+
+ .loader-container {
+ display: flex;
+ flex-direction: column;
+ width: 106px;
+ align-items: center;
+ padding: 16px;
+ background: rgb(255 255 255 / 39%);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ border-radius: 8px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
+ }
+
+ .ios-gear-loading {
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ border: 5px solid rgba(0, 0, 0, 0.2);
+ border-top: 5px solid #2196f3;
+ animation: spin 1s linear infinite;
+ margin-bottom: 10px;
+ }
+
+ @keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/Loader/Loader.tsx b/admin-client/src/components/UIComponents/Loader/Loader.tsx
new file mode 100644
index 0000000..e6ca1a1
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Loader/Loader.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './Loader.scss';
+
+interface LoaderProps {
+ isLoading: boolean;
+}
+
+const Loader: React.FC = ({ isLoading }) => {
+ if (!isLoading) return null;
+
+ return ReactDOM.createPortal(
+
+
+
+
Loading...
+
Free service takes longer than usual to load
+
+
,
+ document.getElementById('loader')!
+ );
+};
+
+export default Loader;
diff --git a/admin-client/src/components/UIComponents/LoaderComponent/LoaderComponent.scss b/admin-client/src/components/UIComponents/LoaderComponent/LoaderComponent.scss
new file mode 100644
index 0000000..e905c49
--- /dev/null
+++ b/admin-client/src/components/UIComponents/LoaderComponent/LoaderComponent.scss
@@ -0,0 +1,46 @@
+
+/* Loader.css */
+.loader-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(123, 123, 123, 0.5);
+ z-index:999999;
+ }
+
+ .loader-container {
+ display: flex;
+ flex-direction: column;
+ width: 106px;
+ align-items: center;
+ padding: 16px;
+ background: rgb(255 255 255 / 39%);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ border-radius: 8px;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
+ }
+
+ .ios-gear-loading {
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ border: 5px solid rgba(0, 0, 0, 0.2);
+ border-top: 5px solid #2196f3;
+ animation: spin 1s linear infinite;
+ margin-bottom: 10px;
+ }
+
+ @keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/LoaderComponent/LoaderComponent.tsx b/admin-client/src/components/UIComponents/LoaderComponent/LoaderComponent.tsx
new file mode 100644
index 0000000..fb4b956
--- /dev/null
+++ b/admin-client/src/components/UIComponents/LoaderComponent/LoaderComponent.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import './LoaderComponent.scss';
+
+const LoaderComponent: React.FC = () => {
+
+ return (
+
+
+
+
Loading...
+
** Free service takes longer than usual to load **
+
+
+ );
+};
+
+export default LoaderComponent;
diff --git a/admin-client/src/components/UIComponents/MenuButton/MenuButton.scss b/admin-client/src/components/UIComponents/MenuButton/MenuButton.scss
new file mode 100644
index 0000000..e69de29
diff --git a/admin-client/src/components/UIComponents/MenuButton/MenuButton.tsx b/admin-client/src/components/UIComponents/MenuButton/MenuButton.tsx
new file mode 100644
index 0000000..40d050b
--- /dev/null
+++ b/admin-client/src/components/UIComponents/MenuButton/MenuButton.tsx
@@ -0,0 +1,111 @@
+import React from "react";
+import { motion, Transition } from "framer-motion";
+import './MenuButton.scss'
+import { useSelector } from "react-redux";
+import { RootState } from "../../../ReduxStore/store";
+import { useDispatch } from "react-redux";
+import { toggleMobSidebar } from "../../../ReduxStore/UISlice";
+
+interface Props {
+ color?: string;
+ strokeWidth?: number;
+ transition?: Transition;
+ lineProps?: any;
+ width?: number;
+ height?: number;
+}
+
+const MenuButton: React.FC = ({
+ width = 24,
+ height = 24,
+ strokeWidth = 1,
+ color = "#000",
+ transition = null,
+ lineProps = null,
+ ...props
+}) => {
+ const isMobSidebarOpen = useSelector((state: RootState) => state.UI.isMobSidebarOpen);
+ const dispatch = useDispatch()
+ const variant = isMobSidebarOpen ? "opened" : "closed";
+ const top = {
+ closed: {
+ rotate: 0,
+ translateY: 0,
+ },
+ opened: {
+ rotate: 45,
+ translateY: 2,
+ },
+ };
+ const center = {
+ closed: {
+ opacity: 1,
+ },
+ opened: {
+ opacity: 0,
+ },
+ };
+ const bottom = {
+ closed: {
+ rotate: 0,
+ translateY: 0,
+ },
+ opened: {
+ rotate: -45,
+ translateY: -2,
+ },
+ };
+ lineProps = {
+ stroke: color,
+ strokeWidth: strokeWidth,
+ vectorEffect: "non-scaling-stroke",
+ initial: "closed",
+ animate: variant,
+ transition,
+ ...lineProps,
+ };
+ const unitHeight = 4;
+ const unitWidth = (unitHeight * (width)) / (height);
+
+
+
+
+ return (
+ dispatch(toggleMobSidebar())}
+ >
+
+
+
+
+ );
+};
+
+export { MenuButton };
diff --git a/admin-client/src/components/UIComponents/MinusIcon/MinusIcon.scss b/admin-client/src/components/UIComponents/MinusIcon/MinusIcon.scss
new file mode 100644
index 0000000..b44ac94
--- /dev/null
+++ b/admin-client/src/components/UIComponents/MinusIcon/MinusIcon.scss
@@ -0,0 +1,73 @@
+@import '../../../styles/typography/typography.scss';
+
+// AddIcon component styles
+.minus_icon {
+ position: relative;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ // transition: background-color 0.2s ease-in-out;
+ border: 2px solid rgba(255, 255, 255, 0.24);
+
+
+ // Default size
+ width: 25px;
+ height: 25px;
+
+ // Optional size passed as a prop
+
+ &.large {
+ width: 40px;
+ height: 40px;
+ }
+
+ // Optional background color passed as a prop
+ &.custom-background {
+ background-color: lightblue;
+ }
+
+ &.darkMode{
+ border: 3px solid $link_blue;
+ }
+
+ .minus_line1{
+ width: 70%;
+ height: 4px;
+ background: rgba(255, 255, 255, 0.68);
+ backdrop-filter: blur(4px);
+ border-radius: 10px;
+
+ &.darkMode{
+ background: $link_blue;
+ }
+ }
+
+ .line2 {
+ position: absolute;
+ transform: rotate(90deg);
+ }
+
+ #minus_tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(0, 0, 0, 0.8);
+ color: #fff;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ // transition: opacity 0.2s ease-in-out;
+ }
+
+
+ &:hover #minus_tooltip {
+ opacity: 1;
+ }
+ }
+
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/MinusIcon/MinusIcon.tsx b/admin-client/src/components/UIComponents/MinusIcon/MinusIcon.tsx
new file mode 100644
index 0000000..f152f1a
--- /dev/null
+++ b/admin-client/src/components/UIComponents/MinusIcon/MinusIcon.tsx
@@ -0,0 +1,78 @@
+import React, { FC, CSSProperties, MouseEventHandler } from 'react';
+import './MinusIcon.scss'
+
+interface MinusIconProps {
+ onClick?: MouseEventHandler;
+ darkMode?: boolean;
+ backgroundColor?: string;
+ size?: number;
+ tooltipText?: string;
+}
+
+const MinusIcon: FC = ({
+ backgroundColor = 'none',
+ size = 25,
+ tooltipText = 'Remove',
+ darkMode = false,
+ onClick = () => { },
+}) => {
+ const iconStyle: CSSProperties = {
+ width: size,
+ height: size,
+ borderRadius: '50%',
+ background: backgroundColor,
+ backdropFilter: 'blur(4px)',
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ };
+
+ const tooltipStyle: CSSProperties = {
+ position: 'absolute',
+ bottom: '-112%',
+ left: '50%',
+ transform: 'translateX(-50%)',
+ background: 'rgba(0, 0, 0, 0.8)',
+ color: '#fff',
+ padding: '4px 8px',
+ borderRadius: '4px',
+ fontSize: '12px',
+ fontWeight: '400',
+ whiteSpace: 'nowrap',
+ pointerEvents: 'none',
+ opacity: 0,
+ // transition: 'opacity 0.2s ease-in-out',
+ };
+
+ const handleMouseEnter = () => {
+ const tooltip = document.getElementById('minus_tooltip');
+ if (tooltip) {
+ tooltip.style.opacity = '1';
+ }
+ };
+
+ const handleMouseLeave = () => {
+ const tooltip = document.getElementById('minus_tooltip');
+ if (tooltip) {
+ tooltip.style.opacity = '0';
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default MinusIcon;
diff --git a/admin-client/src/components/UIComponents/Modal/DesignComponents/GlassmorphicBackground.scss b/admin-client/src/components/UIComponents/Modal/DesignComponents/GlassmorphicBackground.scss
new file mode 100644
index 0000000..c51b104
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Modal/DesignComponents/GlassmorphicBackground.scss
@@ -0,0 +1,14 @@
+.glassmorphic-background {
+ background: rgb(255 255 255 / 68%);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(4px);
+ border: 1px solid rgb(141 141 141);
+ outline: 1px solid black;
+ border-radius: 10px;
+ padding: 20px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+
+ &.dark{
+ background: rgb(55 54 54 / 25%);
+ }
+}
diff --git a/admin-client/src/components/UIComponents/Modal/DesignComponents/GlassmorphicBackground.tsx b/admin-client/src/components/UIComponents/Modal/DesignComponents/GlassmorphicBackground.tsx
new file mode 100644
index 0000000..101435c
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Modal/DesignComponents/GlassmorphicBackground.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import './GlassmorphicBackground.scss';
+import { includeDarkClass } from '../../../../CONFIG';
+import { useSelector } from 'react-redux';
+import { RootState } from '../../../../ReduxStore/store';
+
+interface GlassmorphicBackgroundProps {
+ children: React.ReactNode;
+}
+
+const GlassmorphicBackground: React.FC = ({ children }) => {
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+ return (
+
+ {children}
+
+ );
+};
+
+export default GlassmorphicBackground;
diff --git a/admin-client/src/components/UIComponents/Modal/Modal.scss b/admin-client/src/components/UIComponents/Modal/Modal.scss
new file mode 100644
index 0000000..e46272e
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Modal/Modal.scss
@@ -0,0 +1,114 @@
+// Mobile-first styles
+@import "../../WRAPPERS/DashboardWrapper/DashboardWrapper.scss";
+
+.modal-overlay {
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgb(0 0 0 / 40%);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(px);
+ z-index: 1;
+}
+
+.modal {
+ background: rgb(255 255 255 / 30%);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid rgba(255, 255, 255, 0.3490196078);
+ outline: 1px solid rgba(0, 0, 0, 0.2117647059);
+ padding: 35px 15px;
+ border-radius: 10px;
+ min-width: 300px;
+ max-width: 80%;
+ position: relative;
+ top: 80px;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+
+ &.dark {
+ background: $dash_contents_dark_mode_bgColor;
+ border: $dash_contents_dark_mode_border;
+ outline: $dash_contents_dark_mode_outline;
+ }
+
+ .header_and_cosebtn_container {
+ .truncate {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ h3 {
+ margin: 0 0 10px 0;
+ font-weight: 400;
+ }
+
+ .modal_close_btn {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ }
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+
+ .input_field {
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+ }
+
+ .btn_grp {
+ display: flex;
+ justify-content: center;
+ column-gap: 10px;
+
+ button:nth-child(1) {
+ background-color: rgb(156, 255, 145);
+ }
+ }
+ }
+
+ .modal_main_content_container {
+ -webkit-backdrop-filter: blur(12px);
+ border-radius: 10px;
+ backdrop-filter: blur(1px);
+ padding: 10px;
+
+ &.dark {
+ background-color: #ffffff4f;
+ }
+ }
+
+ @media screen and (max-device-width: 650px) {
+ max-width: 90%;
+ }
+}
+
+.close-button {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ cursor: pointer;
+}
+
+// Responsive styles
+// @media screen and (min-width: 768px) {
+// .modal {
+// width: 60%;
+// }
+// }
+
+// @media screen and (min-width: 1024px) {
+// .modal {
+// width: 80%;
+// }
+// }
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/Modal/Modal.tsx b/admin-client/src/components/UIComponents/Modal/Modal.tsx
new file mode 100644
index 0000000..4c17e50
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Modal/Modal.tsx
@@ -0,0 +1,60 @@
+import React, { FC, ReactNode } from 'react';
+import ReactDOM from 'react-dom';
+import './Modal.scss';
+// import AddIcon from '../AddIcon/AddIcon';
+import CrossIcon from '../CrossIcon/CrossIcon';
+import { includeDarkClass } from '../../../CONFIG';
+import { RootState } from '../../../ReduxStore/store';
+import { useSelector } from 'react-redux';
+
+interface ModalProps {
+ isOpen: Boolean;
+ onClose: () => void;
+ children: ReactNode;
+ heading: string;
+ mountOnPortal?: boolean;
+}
+
+const Modal: FC = ({ isOpen, onClose, children, heading = "MOdal header Modal header" }, mountOnPortal) => {
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+ if (!isOpen) return null;
+
+ if (mountOnPortal) {
+ return ReactDOM.createPortal(
+
+
e.stopPropagation()}>
+
+
+
{heading}
+
+
+ { }} />
+
+
+
{children}
+
+
,
+ document.getElementById('modal-root')!
+ );
+ } else {
+ return (
+
+
e.stopPropagation()}>
+
+
+
{heading}
+
+
+ { }} />
+
+
+
{children}
+
+
+ );
+ }
+
+
+};
+
+export default Modal;
diff --git a/admin-client/src/components/UIComponents/NoofSubTodos.scss b/admin-client/src/components/UIComponents/NoofSubTodos.scss
new file mode 100644
index 0000000..8bc18f0
--- /dev/null
+++ b/admin-client/src/components/UIComponents/NoofSubTodos.scss
@@ -0,0 +1,10 @@
+.NoofSubTodos_maindiv{
+ display: flex;
+ background-color: #ffffff;
+ color: #8a8122;
+ font-size: 28px;
+ border-radius: 5px;
+ padding: 0 5px;
+ gap: 5px;
+ border:solid 1px rgb(46, 45, 45)
+}
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/NoofSubtodos.tsx b/admin-client/src/components/UIComponents/NoofSubtodos.tsx
new file mode 100644
index 0000000..b121037
--- /dev/null
+++ b/admin-client/src/components/UIComponents/NoofSubtodos.tsx
@@ -0,0 +1,15 @@
+import { childTodo } from "../../medias"
+import "./NoofSubTodos.scss"
+
+interface NoofSubtodosProps{
+ subTodoNumber:Number
+}
+
+const NoofSubtodos : React.FC = ({subTodoNumber})=>{
+ return(
+
+
{subTodoNumber.toLocaleString()}
+ )
+}
+
+export default NoofSubtodos;
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/PasswordInput.scss b/admin-client/src/components/UIComponents/PasswordInput.scss
new file mode 100644
index 0000000..f178a91
--- /dev/null
+++ b/admin-client/src/components/UIComponents/PasswordInput.scss
@@ -0,0 +1,21 @@
+.passwordInput {
+ position: relative;
+ .inputFeild{
+ position:relative;
+ .show_hide {
+ position: absolute;
+ top: 3px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin: 0 5px;
+ right: 5px;
+ img {
+ width: 20px;
+ height:20px;
+ cursor:pointer
+ }
+ }
+ }
+}
diff --git a/admin-client/src/components/UIComponents/PasswordInput.tsx b/admin-client/src/components/UIComponents/PasswordInput.tsx
new file mode 100644
index 0000000..60a63a5
--- /dev/null
+++ b/admin-client/src/components/UIComponents/PasswordInput.tsx
@@ -0,0 +1,42 @@
+import React, { useState } from 'react';
+import "./PasswordInput.scss"
+import { hide, show } from '../../medias';
+
+interface PasswordInputProps {
+ label: string;
+ id:string;
+ value:string;
+ onChange:any;
+ required?: any;
+}
+
+const PasswordInput: React.FC = ({ label, id, value, onChange, required = false }) => {
+ const [showPassword, setShowPassword] = useState(false);
+
+ const handleInputChange = (event: React.ChangeEvent) => {
+ onChange(event)
+ };
+
+ const togglePasswordVisibility = () => {
+ setShowPassword(!showPassword);
+ };
+
+ return (
+
+
{label}
+
+
+
+ {showPassword ?
:
}
+
+
+ );
+};
+
+export default PasswordInput;
diff --git a/admin-client/src/components/UIComponents/Profile/Profile.scss b/admin-client/src/components/UIComponents/Profile/Profile.scss
new file mode 100644
index 0000000..fe8bf86
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Profile/Profile.scss
@@ -0,0 +1,123 @@
+.profile_main_container {
+ flex: 1;
+ border-radius: 10px;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+
+ @media screen and (min-device-width: 350px) and (max-device-width: 650px) {
+ width: 100%;
+ }
+
+ .header {
+ font-size: 20px;
+ font-weight: 400;
+ color: rgba(0, 0, 0, 0.49);
+ &.dark {
+ color: rgb(255 255 255 / 49%);
+ }
+ }
+ .content {
+ word-wrap: break-word;
+ font-weight: 400;
+ }
+
+ .horizontal_divider {
+ width: 100%;
+ height: 1px;
+ background: rgba(255, 255, 255, 0.446);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ }
+
+ .profile_main_card {
+ background-color: rgba(255, 255, 255, 0.52);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ padding: 10px;
+ border-radius: 10px;
+ margin: 5% 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 300px;
+ max-width: 365px;
+ box-shadow: 0px 0px 2px;
+ outline: 1px solid rgb(0 0 0 / 61%);
+ border: 1px solid #00000040;
+
+ .edit_profile_icon {
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ top: 6px;
+ right: 7px;
+ background-color: rgba(255, 255, 255, 0.513);
+ padding: 5px;
+ border-radius: 5px;
+ path {
+ fill: #0080ff;
+ }
+ &.dark {
+ background-color: unset;
+ }
+ }
+
+ &.dark {
+ background-color: rgba(0, 0, 0, 0.52);
+ }
+
+ .profile_pic_con {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ row-gap: 10px;
+ margin: 10px 0px;
+ width: 100%;
+ img,
+ svg {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ outline: 1px solid #0000003b;
+ margin-bottom: 10px;
+ }
+ &.light {
+ svg {
+ fill: white;
+ }
+ }
+ .modal{
+ form{
+ display:flex;
+ .input_feild{
+ display:flex;
+ }
+ }
+ }
+ @media screen and (min-device-width: 350px) and (max-device-width: 650px) {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ row-gap: 10px;
+ margin: 10px 0 40px 0;
+ width: 100%;
+ }
+ }
+
+ .profile_userName_con,
+ .profile_email,
+ .profile_picUrl {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ row-gap: 5px;
+ margin: 5px 0 20px 0;
+ }
+ @media screen and (min-device-width: 350px) and (max-device-width: 650px) {
+ min-width: unset;
+ max-width: unset;
+ width: 88%;
+ }
+ }
+}
diff --git a/admin-client/src/components/UIComponents/Profile/Profile.tsx b/admin-client/src/components/UIComponents/Profile/Profile.tsx
new file mode 100644
index 0000000..edc8876
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Profile/Profile.tsx
@@ -0,0 +1,195 @@
+import { useSelector } from "react-redux";
+import "./Profile.scss";
+import { RootState } from "../../../ReduxStore/store";
+import { getUrl, includeDarkClass } from "../../../CONFIG";
+import { useDispatch } from "react-redux";
+import { useEffect, useState } from "react";
+import { setCurrentPage, setLoading } from "../../../ReduxStore/UISlice";
+import { motion } from "framer-motion";
+import LoaderComponent from "../LoaderComponent/LoaderComponent";
+import UserProfile from "../../../medias/UserProfile";
+import EditUserProfile from "../../../medias/EditUserProfile";
+import Modal from "../Modal/Modal";
+
+interface ProfileProps {
+ fetchAllUserData: any;
+ handleLogout: any;
+}
+
+const Profile: React.FC = ({ fetchAllUserData = () => { } ,handleLogout=() => { }}) => {
+ const userProfile = useSelector((state: RootState) => state.User.allUserData);
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark);
+ const token = useSelector((state: RootState) => state.User.token);
+ const [isOpen, setIsOpen] = useState(false)
+ const [userName, setUserName] = useState(userProfile.userName);
+ const [userEmail, setUserEmail] = useState(userProfile.email);
+ const [userPicUrl, setUserPicUrl] = useState(userProfile.picUrl);
+
+
+ const dispatch = useDispatch();
+
+ const handleProfileUpdate = (e: React.FormEvent) => {
+ e.preventDefault();
+ const formData = new FormData();
+ if (userName && userName !== userProfile.userName) {
+ formData.append('userName', userName)
+ }
+ if (userEmail && userEmail !== userProfile.email) {
+ formData.append('email', userEmail)
+ }
+ if (userPicUrl && userPicUrl !== userProfile.picUrl) {
+ formData.append('picUrl', userPicUrl)
+ }
+ console.log({
+ userName: userName,
+ email: userEmail,
+ picUrl: userPicUrl
+ })
+ dispatch(setLoading(true))
+ fetch(getUrl('/auth/profile'), {
+ method: 'PUT',
+ body: formData,
+ headers: {
+ 'Authorization': `${token}`,
+ }
+ }).then(res => {
+ if (res.ok) {
+ console.log("user updated successfully")
+ fetchAllUserData(token)
+ setIsOpen(false)
+ }
+ console.log("error in updating user")
+ return res.json()
+ }).then((jsonRes) => {
+ console.log(jsonRes)
+ }).finally(() => {
+ console.log("finally")
+ })
+ dispatch(setLoading(false))
+ }
+ useEffect(() => {
+ dispatch(setCurrentPage("Your Profile Info"));
+ }, [dispatch]);
+ useEffect(() => {
+ console.log("Profile component Called");
+ }, []);
+
+ useEffect(() => {
+ if (userProfile) {
+ setUserName(userProfile.userName);
+ setUserEmail(userProfile.email);
+ setUserPicUrl(userProfile.picUrl);
+ }
+ }, [userProfile]);
+
+ return (
+
+ {userProfile &&
+ userProfile.userName &&
+ userProfile.email &&
+ userProfile.picUrl ? (
+
+
+
+
+ {" "}
+ Logout
+
+
+
+ {userProfile.picUrl ? (
+
+ ) : (
+
+ )}
+
+ {/*
*/}
+
+ Profile Picture
+
+
+
+
+
+ UserName :
+
+
+ {userProfile.userName}
+
+
+
+
+
+ Email :
+
+
+ {userProfile.email}
+
+
+
+
+
+ Profile Pic Url
+
+
+ {userProfile.picUrl}
+
+
+
+ setIsOpen(!isOpen)} className={includeDarkClass("edit_profile_icon", darkMode)}>
+
+
+ {userProfile && userProfile.userName && userProfile.email && userProfile.picUrl ? setIsOpen(!isOpen)} heading="Edit Profile" >
+
+ : <>>}
+
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default Profile;
diff --git a/admin-client/src/components/UIComponents/TodoDetails/TodoDetails.scss b/admin-client/src/components/UIComponents/TodoDetails/TodoDetails.scss
new file mode 100644
index 0000000..5791c6d
--- /dev/null
+++ b/admin-client/src/components/UIComponents/TodoDetails/TodoDetails.scss
@@ -0,0 +1,117 @@
+.todo_details_container {
+ border-radius: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+ height: auto;
+ background-color: rgb(255 255 255 / 10%);
+ // padding:10px;
+
+ .todo_id {
+ font-size: 16px;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ width: calc(100% - 20px);
+ border-radius: 10px 10px 0 0;
+ padding: 10px;
+ background: rgba(255, 255, 255, 0.5);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ flex-direction: row;
+ justify-content: space-between;
+
+ &.dark {
+ background: rgb(125 125 125 / 50%);
+ }
+ }
+
+ .horizontal_line {
+ width: 100%;
+ height: 1px;
+ background: rgba(255, 255, 255, 0.19);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ }
+
+ .todo_contents_container {
+ width: calc(100% - 20px);
+ margin: 5px;
+ -webkit-backdrop-filter: blur(1px);
+ backdrop-filter: blur(10px);
+ border-radius: 5px;
+ padding: 5px;
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+ flex: 1 1;
+ background-color: rgba(255, 255, 255, 0.4);
+
+ &.dark{
+ background-color: unset;
+ }
+
+ .header {
+ font-size: 12px;
+ font-weight: 400;
+ color: rgb(0 0 0 / 71%);
+ &.dark{
+ color:rgb(255 255 255 / 44%)
+ }
+ }
+
+ .content {
+ padding: 3px 0 10px 0;
+ font-size: 16px;
+ word-wrap: break-word;
+ }
+
+
+
+ .todo_date_and_time_container {
+ display: flex;
+ column-gap: 20px;
+
+ .todo_createdAt,
+ .todo_updatedAt {
+ background-color: rgb(255 255 255 / 10%);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ padding: 7px;
+ border-radius: 5px;
+ display: flex;
+ font-size: 12px;
+ flex-direction: column;
+ align-items: center;
+
+ .header{
+ margin-bottom: 5px;
+ }
+
+
+ .time {
+ padding: 0 10px;
+ }
+ }
+
+ }
+
+ .todo_subTodos {
+ .subTodo_addbtn {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+ }
+
+ .btn_grp {
+ display: flex;
+ justify-content: center;
+ column-gap: 10px;
+ button:nth-child(1){
+ background-color: rgb(156, 255, 145);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/TodoDetails/TodoDetails.tsx b/admin-client/src/components/UIComponents/TodoDetails/TodoDetails.tsx
new file mode 100644
index 0000000..592a8e6
--- /dev/null
+++ b/admin-client/src/components/UIComponents/TodoDetails/TodoDetails.tsx
@@ -0,0 +1,494 @@
+import { useParams } from "react-router-dom";
+import "./TodoDetails.scss";
+import { useSelector } from "react-redux";
+import { RootState } from "../../../ReduxStore/store";
+import { formatDateAndTime, getUrl, includeDarkClass } from "../../../CONFIG";
+import { useEffect, useState } from "react";
+import TodosListContainer from "../Todos/TodosListContainer/TodosListContainer";
+import Modal from "../Modal/Modal";
+import AddIcon from "../AddIcon/AddIcon";
+import { useDispatch } from "react-redux";
+import { setCurrentPage, setLoading } from "../../../ReduxStore/UISlice";
+import LoaderComponent from "../LoaderComponent/LoaderComponent";
+import CTAIconWrapper from "../../WRAPPERS/CTAIconWrapper/CTAIconWrapper";
+import Editsvg from "../../../medias/Editsvg";
+
+interface TodoItem {
+ createdAt: string;
+ description: string;
+ possibleStatus: object;
+ possiblePriority: object;
+ title: string;
+ todo: TodoItem[];
+ updatedAt: string;
+ status: string;
+ priority: string;
+ user: string;
+ todoId?:string;
+ parentTodo_id?:string;
+ __v: number;
+ _id: string;
+}
+interface TodoDetailsProps {
+ parentTodo_id?:string;
+}
+const status = ['Todo', 'InProgress', 'Completed', 'OnHold'];
+const priority = ['High', 'Medium', 'Low'];
+const TodoDetails: React.FC>= ({parentTodo_id=''}) => {
+ const [todo, setTodo] = useState(null);
+ const [createdAtdateAndTime, setCreatedAtDateAndTime] =
+ useState | null>(null);
+ const [updatedAtdateAndTime, setUpdatedAtDateAndTime] =
+ useState | null>(null);
+ const [isOpen, setIsOpen] = useState(false);
+ const [isEditing, setIsEditing] = useState(false);
+ const [subTodoTitleInput, setSubTodoTitleInput] = useState(
+ null
+ );
+ const [subTodoDescInput, setSubTodoDescInput] = useState(null);
+ // State for input fields
+ const [titleInput, setTitleInput] = useState("");
+ const [descriptionInput, setDescriptionInput] = useState("");
+ const params = useParams();
+ const dispatch = useDispatch();
+ const token = useSelector((state: RootState) => state.User.token);
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark);
+ const finalParentTodo_id = params.parentTodo_id ? params.parentTodo_id : parentTodo_id
+ const update = async (changeObj: any,) => {
+ dispatch(setLoading(true));
+ try {
+ if (token !== null) {
+ const formData = new FormData();
+ if (params.childTodo_id) {
+ // Update a childTodo
+ formData.append("todoId", params.childTodo_id);
+ formData.append(
+ "changeObj",
+ changeObj
+ );
+ const response = await fetch(getUrl("/admin/putSubTodo"), {
+ method: "PUT",
+ body: formData,
+ headers: {
+ Authorization: token,
+ },
+ });
+ if (!response.ok) {
+ dispatch(setLoading(false));
+ setIsEditing(false);
+ setIsOpen(false);
+ throw new Error("Request failed");
+ }
+ // Fetch updated childTodo after successful API call
+ fetchChildTodo(params.childTodo_id, token);
+ } else if (finalParentTodo_id) {
+ // Update a parentTodo
+ formData.append("todoId", finalParentTodo_id);
+ formData.append(
+ "changeObj",
+ changeObj
+ );
+ const response = await fetch(getUrl("/admin/putTodo"), {
+ method: "PUT",
+ body: formData,
+ headers: {
+ Authorization: token,
+ },
+ });
+ if (!response.ok) {
+ dispatch(setLoading(false));
+ setIsEditing(false);
+ setIsOpen(false);
+ throw new Error("Request failed");
+ }
+ // Fetch updated parentTodo after successful API call
+ fetchParentTodo(finalParentTodo_id, token);
+ }
+ dispatch(setLoading(false));
+ setIsEditing(false);
+ setIsOpen(false);
+ }
+ } catch (err) {
+ console.error("Error:", err);
+ dispatch(setLoading(false));
+ setIsEditing(false);
+ setIsOpen(false);
+ }
+ }
+
+ const updateStatus = async (
+ event: React.ChangeEvent
+ ) => {
+ event.preventDefault();
+ try {
+ if (token !== null) {
+ if (params.childTodo_id) {
+ // Update a childTodo
+ const chnageObj = JSON.stringify({
+ status: event.target.value,
+ })
+ update(chnageObj)
+ } else if (finalParentTodo_id) {
+ // Update a parentTodo
+ const chnageObj = JSON.stringify({
+ status: event.target.value,
+ })
+ update(chnageObj)
+ }
+ }
+ } catch (err) {
+ console.error("Error:", err);
+ }
+ };
+ const updatePriority = async (
+ event: React.ChangeEvent
+ ) => {
+ event.preventDefault();
+ try {
+ if (token !== null) {
+ if (params.childTodo_id) {
+ // Update a childTodo
+ const chnageObj = JSON.stringify({
+ priority: event.target.value,
+ })
+ update(chnageObj)
+ } else if (finalParentTodo_id) {
+ // Update a parentTodo
+ const chnageObj = JSON.stringify({
+ priority: event.target.value,
+ })
+ update(chnageObj)
+ }
+ }
+ } catch (err) {
+ console.error("Error:", err);
+ }
+ };
+
+ const fetchParentTodo = (TodoId: string, token: string) => {
+ const formData = new FormData();
+ formData.append("todoId", TodoId);
+ // console.log("Called");
+ if (token)
+ fetch(getUrl("/admin/postGetTodo"), {
+ method: "POST",
+ body: formData,
+ headers: {
+ Authorization: token,
+ },
+ })
+ .then((res) => {
+ if (res.ok) {
+ return res.json();
+ } else {
+ throw new Error("something went wrong");
+ }
+ })
+ .then((jsonData) => {
+ // console.log(jsonData);
+ setTodo(jsonData);
+ setCreatedAtDateAndTime(
+ formatDateAndTime(new Date(jsonData.createdAt))
+ );
+ setUpdatedAtDateAndTime(
+ formatDateAndTime(new Date(jsonData.updatedAt))
+ );
+ });
+ };
+ const fetchChildTodo = (TodoId: string, token: string) => {
+ const formData = new FormData();
+ formData.append("todoId", TodoId);
+ if (token)
+ fetch(getUrl("/admin/postGetSubTodo"), {
+ method: "POST",
+ body: formData,
+ headers: {
+ Authorization: token,
+ },
+ })
+ .then((res) => {
+ if (res.ok) {
+ return res.json();
+ } else {
+ throw new Error("something went wrong");
+ }
+ })
+ .then((jsonData) => {
+ setTodo(jsonData);
+ setCreatedAtDateAndTime(
+ formatDateAndTime(new Date(jsonData.createdAt))
+ );
+ setUpdatedAtDateAndTime(
+ formatDateAndTime(new Date(jsonData.updatedAt))
+ );
+ });
+ };
+
+ const handleAddSubTodo = async (event: React.FormEvent) => {
+ event.preventDefault();
+ dispatch(setLoading(true));
+ if (subTodoTitleInput && subTodoDescInput && finalParentTodo_id) {
+ const formData = new FormData();
+ formData.append("parentId", finalParentTodo_id);
+ formData.append("subTodoTitle", subTodoTitleInput);
+ formData.append("subTodoDescription", subTodoDescInput);
+
+ try {
+ if (token !== null) {
+ const response = await fetch(getUrl("/admin/postSubTodo"), {
+ method: "POST",
+ body: formData,
+ headers: {
+ Authorization: token,
+ },
+ });
+ if (!response.ok) {
+ dispatch(setLoading(false));
+
+ setIsOpen(false);
+ throw new Error("Request failed");
+ }
+ dispatch(setLoading(false));
+ setIsOpen(false);
+ fetchParentTodo(finalParentTodo_id, token);
+ }
+ } catch (err) {
+ console.error("Error:", err);
+ dispatch(setLoading(false));
+ setIsOpen(false);
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (todo && todo.title && todo.description) {
+ setTitleInput(todo.title);
+ setDescriptionInput(todo.description);
+ }
+ }, [todo]);
+
+ const handleUpdateSubmit = async (
+ event: React.FormEvent
+ ) => {
+ event.preventDefault();
+ try {
+ if (token !== null) {
+ if (params.childTodo_id) {
+ // Update a childTodo
+ const chnageObj = JSON.stringify({
+ title: titleInput,
+ description: descriptionInput,
+ })
+ update(chnageObj)
+ } else if (finalParentTodo_id) {
+ // Update a parentTodo
+ const chnageObj = JSON.stringify({
+ title: titleInput,
+ description: descriptionInput,
+ })
+ update(chnageObj)
+ }
+ }
+ } catch (err) {
+ console.error("Error:", err);
+ }
+ };
+
+ useEffect(() => {
+ if (token) {
+ if (finalParentTodo_id && !params.childTodo_id) {
+ fetchParentTodo(finalParentTodo_id, token);
+ } else if (finalParentTodo_id && params.childTodo_id) {
+ fetchChildTodo(params.childTodo_id, token);
+ dispatch(setCurrentPage("Sub-Todo Details"));
+ }
+ }
+ }, [dispatch, params.childTodo_id, finalParentTodo_id, token]);
+ if (!todo) {
+ return ;
+ }
+ return (
+
+ );
+};
+
+export default TodoDetails;
diff --git a/admin-client/src/components/UIComponents/Todos/TodoItem/TodoItem.scss b/admin-client/src/components/UIComponents/Todos/TodoItem/TodoItem.scss
new file mode 100644
index 0000000..722df93
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Todos/TodoItem/TodoItem.scss
@@ -0,0 +1,133 @@
+@import '../../../WRAPPERS/./DashboardWrapper/DashboardWrapper.scss';
+
+.todo_item_individual {
+ font-size: 24px;
+ font-weight: 400;
+ width: calc(100% - 20px);
+ background: rgba(255, 255, 255, 0.4);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(10px);
+ border-radius: 10px;
+ padding: 3px 10px;
+ display: flex;
+ align-items: center;
+ margin: 5px 0;
+ box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.4);
+ overflow: hidden;
+ flex-direction: column;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ gap: 10px;
+
+
+ .status_mark {
+ width: 100%;
+
+ position: absolute;
+ height: 2px;
+ bottom: 0px;
+ left: 0;
+
+ &.Todo {
+ background-color: rgba(255, 225, 0, 0.684);
+ }
+
+ &.InProgress {
+ background-color: #0080ff;
+ }
+
+ &.Completed {
+ background-color: rgba(77, 255, 0, 0.684);
+ }
+
+ &.OnHold {
+ background-color: rgba(255, 136, 0, 0.684);
+ }
+ }
+
+ &.dark {
+ box-shadow: inset 0px 0px 2px rgba(255, 255, 255, 0.4);
+ background: $dash_contents_dark_mode_bgColor;
+ outline: $dash_contents_dark_mode_outline;
+ border: $dash_contents_dark_mode_border;
+ border: 0.5px;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ }
+
+ &:hover {
+ box-shadow: inset 0px 0px 5px;
+ }
+
+ .date_and_time_ctas_container {
+ display: flex;
+ width: -webkit-fill-available;
+ justify-content: space-evenly;
+
+ .date_and_time {
+ background-color: rgba(255, 255, 255, 0.2);
+ padding: 3px 3px;
+ border-radius: 10px;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 80px;
+ justify-content: center;
+ border-radius: 50px;
+ background: linear-gradient(145deg, #cacaca, #f0f0f0);
+ box-shadow: 20px 20px 60px #bebebe,
+ -20px -20px 60px #ffffff;
+
+
+ .text {
+ font-size: 8px;
+ font-weight: 900;
+ color: #3c3c3c;
+
+ &.dark {
+ color: rgb(255, 255, 255, 90%);
+ }
+ }
+ }
+
+ .todo_CTAs_container {
+ max-width: 100px;
+ width: 80px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-left: 5px;
+ justify-content: space-between;
+
+
+ }
+ }
+
+
+
+ .todo_item_title {
+ flex-grow: 1;
+ width: calc(100% - 0px);
+ font-weight: 400;
+ color: rgb(0 0 0);
+ font-size: 16px;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ white-space: wrap !important;
+
+ &.dark {
+ color: rgb(255, 255, 255, 90%);
+ }
+ }
+
+
+}
+
+
+.del_cncl {
+ display: flex;
+ justify-content: space-evenly;
+}
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/Todos/TodoItem/TodoListItem.tsx b/admin-client/src/components/UIComponents/Todos/TodoItem/TodoListItem.tsx
new file mode 100644
index 0000000..9c54673
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Todos/TodoItem/TodoListItem.tsx
@@ -0,0 +1,148 @@
+import React, { useState } from 'react';
+import './TodoItem.scss';
+// import ChevronIcon from '../../Chevron/ChevronIcon';
+import { formatDateAndTime, getUrl, includeDarkClass } from '../../../../CONFIG';
+// import CrossIcon from '../../CrossIcon/CrossIcon';
+import { useSelector } from 'react-redux';
+import { RootState } from '../../../../ReduxStore/store';
+import { setLoading } from '../../../../ReduxStore/UISlice';
+import { useDispatch } from 'react-redux';
+import Modal from '../../Modal/Modal';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion'
+import CTAIconWrapper from '../../../WRAPPERS/CTAIconWrapper/CTAIconWrapper';
+import ChevronRight from '../../../../medias/ChevronRight';
+import CrossIcon from '../../../../medias/CrossIcon';
+import { TodoItem } from '../../../../App';
+import TodoDetails from '../../TodoDetails/TodoDetails';
+
+interface PartialTodo extends Partial { }
+
+interface TodoListItemProps {
+ item: PartialTodo,
+ fetchAllUserData: any,
+ isSubTodo: boolean;
+ parentTodoId: string;
+ fetchParentTodo?: any;
+}
+
+const TodoListItem: React.FC> = ({ item, fetchAllUserData, isSubTodo, parentTodoId = "", fetchParentTodo = () => { } }) => {
+ const token = useSelector((state: RootState) => state.User.token)
+ const theme = useSelector((state: RootState) => state.UI.theme)
+ const [todoItemModalIsOpen,setTodoItemModalIsOpen]= useState(false)
+ const dispatch = useDispatch()
+ const navigate = useNavigate()
+ const [isOpen, setIsOpen] = useState(false)
+ if (!item || !item.title || !item.createdAt) {
+ return null
+ }
+ const createdAtDate = new Date(item.createdAt)
+ const [date, time] = formatDateAndTime(createdAtDate)
+
+
+ const handleParentDelete = () => {
+ if (token) {
+ dispatch(setLoading(true))
+ var formdata = new FormData();
+ if (item && item._id) {
+ formdata.append("todoId", item._id);
+ }
+ fetch(getUrl("/admin/deleteTodo"), {
+ method: 'DELETE',
+ body: formdata,
+ headers: {
+ 'Authorization': token
+ }
+ }).then(response => response.json())
+ .then(result => {
+ fetchAllUserData(token)
+ dispatch(setLoading(false));
+ setIsOpen(false)
+ })
+ .catch(error => console.log('error', error));
+ dispatch(setLoading(false));
+ setIsOpen(false)
+ } else {
+ console.error('No token Present')
+ }
+ }
+ const handleChildTodoDelete = () => {
+ if (token) {
+ dispatch(setLoading(true))
+ var formdata = new FormData();
+ if (item && item._id) {
+ formdata.append("subTodoId", item._id);
+ }
+ formdata.append("parentTodoId", parentTodoId);
+ fetch(getUrl("/admin/deleteSubTodo"), {
+ method: 'DELETE',
+ body: formdata,
+ headers: {
+ 'Authorization': token
+ }
+ }).then(response => response.json())
+ .then(result => {
+ // fetchAllUserData(token)
+ fetchParentTodo(parentTodoId, token)
+ dispatch(setLoading(false));
+ setIsOpen(false)
+ })
+ .catch(error => console.log('error', error));
+ dispatch(setLoading(false));
+ setIsOpen(false)
+ } else {
+ console.error('No token Present')
+ }
+ }
+ const handleRedirect = () => {
+ const id = item._id;
+ if (!isSubTodo) {
+ navigate(`/todos/${id}`)
+ } else {
+ navigate(`/todos/${parentTodoId}/subTodo/${id}`)
+ }
+ }
+ return (
+ setTodoItemModalIsOpen(!todoItemModalIsOpen)}
+ id={item._id} className={`todo_item_individual ${item && item.status ? `${item.status}` : ''} ${theme.dark ? 'dark' : 'light'}`}>
+ {item && item.title}
+
+
+
+ {
+ setIsOpen(!isOpen);
+ }} >
+
+
+
+
+
+
+
+
+ setIsOpen(!isOpen)} heading={`Are you sure you want to delete the "${item.title}" TODO ???`}>
+
+ {
+ if (isSubTodo && parentTodoId) {
+ handleChildTodoDelete()
+ } else {
+ handleParentDelete()
+ }
+ }}>DELETE
+ setIsOpen(!isOpen)}>Cancel
+
+ setTodoItemModalIsOpen(!todoItemModalIsOpen)} heading={`Todo Details!! `}>
+
+
+
+ );
+};
+
+export default TodoListItem;
diff --git a/admin-client/src/components/UIComponents/Todos/TodosListContainer/TodosListContainer.scss b/admin-client/src/components/UIComponents/Todos/TodosListContainer/TodosListContainer.scss
new file mode 100644
index 0000000..6f9c888
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Todos/TodosListContainer/TodosListContainer.scss
@@ -0,0 +1,46 @@
+.todoListItems_container {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ gap: 20px;
+ overflow: scroll;
+ padding: 1px;
+
+ .status_container {
+ display: flex;
+ min-width: 325px;
+ max-width: 500px;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ background: rgba(206, 206, 206, 0.27);
+ backdrop-filter: blur(15px);
+ outline: 0.5px solid #1f2025;
+ border: 1px solid #6d6d6d;
+ border-radius: 10px;
+
+ .title {
+ width: calc(100% - 10px);
+ background-color: #1f2025;
+ padding: 5px;
+ border-radius: 10px 10px 0 0;
+ background-color: rgba(255, 255, 255, 0.099);
+ }
+
+ .all_todos {
+ width: calc(100% - 7px);
+ }
+
+ &.Todo {
+ background-color: rgba(255, 255, 255, 0.099);
+ outline: 0.5px solid #1f2025;
+ border: 1px solid #6d6d6d;
+ }
+
+ &.dark {
+ background-color: rgba(255, 255, 255, 0.099);
+ outline: 0.5px solid #1f2025;
+ border: 1px solid #6d6d6d;
+ }
+ }
+}
\ No newline at end of file
diff --git a/admin-client/src/components/UIComponents/Todos/TodosListContainer/TodosListContainer.tsx b/admin-client/src/components/UIComponents/Todos/TodosListContainer/TodosListContainer.tsx
new file mode 100644
index 0000000..bd7a53a
--- /dev/null
+++ b/admin-client/src/components/UIComponents/Todos/TodosListContainer/TodosListContainer.tsx
@@ -0,0 +1,98 @@
+import React, { useEffect } from "react";
+import './TodosListContainer.scss'
+import TodoItem from "../TodoItem/TodoListItem";
+import { useDispatch } from "react-redux";
+// import { setLoading } from "../../../../ReduxStore/UISlice";
+import LoaderComponent from "../../LoaderComponent/LoaderComponent";
+import { setCurrentPage } from "../../../../ReduxStore/UISlice";
+import { includeDarkClass } from "../../../../CONFIG";
+import { useSelector } from "react-redux";
+import { RootState } from "../../../../ReduxStore/store";
+
+interface TodoListContainerProps {
+ todosArray: {
+ createdAt: string;
+ title: string;
+ todo: any[];
+ updatedAt: string;
+ user: string;
+ __v: number;
+ _id: string;
+ }[],
+ isAllTodosContainer: boolean;
+ fetchAllUserData: any;
+ isSubTodoContainer?: boolean;
+ parentTodoId?: string;
+ fetchParentTodo?: any;
+ className?: string;
+ title?: string;
+ hideParent?: boolean;
+}
+
+// const NoTodosSvg: any = () => dsfdsvsd
+
+export const Container: React.FC> = ({hideParent=false, title = '', className = '', todosArray = [], isSubTodoContainer = false, parentTodoId = '', fetchAllUserData = () => { }, fetchParentTodo = () => { } }) => {
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+ return (
+
+
{title}
+
+ {todosArray && todosArray.length === 0 ? 'NoTodosSvg' : todosArray ? todosArray.map((item, index) => {
+ return (
+ <>
+
+ >
+ )
+ })
+ :
+ }
+
+
+ )
+};
+
+const TodosListContainer: React.FC> = ({ isAllTodosContainer, todosArray, fetchAllUserData, isSubTodoContainer, parentTodoId = "", fetchParentTodo = () => { } }) => {
+
+ const dispatch = useDispatch()
+
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+ const User = useSelector((state: RootState) => state.User.allUserData)
+
+ useEffect(() => {
+ if (isSubTodoContainer) {
+ dispatch(setCurrentPage('Todo Details'))
+ } else {
+ dispatch(setCurrentPage('All Todos'))
+ }
+ }, [dispatch, isSubTodoContainer])
+ return (
+
+ {
+ isAllTodosContainer && todosArray ?
+
+ : isSubTodoContainer && todosArray ?
+
+ : <>
+ {User && User.statusFiltered && User.statusFiltered.__filteredTodos ?
+
+ : <>>
+ }
+ {User && User.statusFiltered && User.statusFiltered.__filteredInProgress ?
+
+ : <>>
+ }
+ {User && User.statusFiltered && User.statusFiltered.__filteredCompleted ?
+
+ : <>>
+ }
+ {User && User.statusFiltered && User.statusFiltered.__filteredOnHold ?
+
+ : <>>
+ }
+ >}
+
+
+ )
+}
+
+export default TodosListContainer
\ No newline at end of file
diff --git a/admin-client/src/components/WRAPPERS/CTAIconWrapper/CTAIconWrapper.scss b/admin-client/src/components/WRAPPERS/CTAIconWrapper/CTAIconWrapper.scss
new file mode 100644
index 0000000..aecfa44
--- /dev/null
+++ b/admin-client/src/components/WRAPPERS/CTAIconWrapper/CTAIconWrapper.scss
@@ -0,0 +1,14 @@
+.icon_Wrapper{
+ width: 24px;
+ height: 24px;
+ background-color: rgba(255, 255, 255, 0.513);
+ padding: 5px;
+ border-radius: 5px;
+ path {
+ fill: #0080ff;
+ }
+ &.dark {
+ background-color: unset;
+ }
+
+}
\ No newline at end of file
diff --git a/admin-client/src/components/WRAPPERS/CTAIconWrapper/CTAIconWrapper.tsx b/admin-client/src/components/WRAPPERS/CTAIconWrapper/CTAIconWrapper.tsx
new file mode 100644
index 0000000..24e5f8d
--- /dev/null
+++ b/admin-client/src/components/WRAPPERS/CTAIconWrapper/CTAIconWrapper.tsx
@@ -0,0 +1,21 @@
+import React from 'react'
+import "./CTAIconWrapper.scss"
+import { includeDarkClass } from '../../../CONFIG'
+import { useSelector } from 'react-redux'
+import { RootState } from '../../../ReduxStore/store'
+
+type Props = {
+ children: React.ReactNode,
+ onClick: any
+}
+
+const CTAIconWrapper: React.FC = ({ children, onClick }) => {
+ const darkMode = useSelector((state: RootState) => state.UI.theme.dark)
+ return (
+
+ {children}
+
+ )
+}
+
+export default CTAIconWrapper
\ No newline at end of file
diff --git a/admin-client/src/components/WRAPPERS/DashboardWrapper/DashboardWrapper.scss b/admin-client/src/components/WRAPPERS/DashboardWrapper/DashboardWrapper.scss
new file mode 100644
index 0000000..f9d33e8
--- /dev/null
+++ b/admin-client/src/components/WRAPPERS/DashboardWrapper/DashboardWrapper.scss
@@ -0,0 +1,424 @@
+@import "../../../styles/typography/typography.scss";
+
+$dash_contents_dark_mode_outline: 0.5px solid #1f2025;
+$dash_contents_dark_mode_border: 1px solid #6d6d6d;
+$dash_contents_dark_mode_bgColor: rgb(55 55 55 / 69%);
+
+.main_dashboard_container {
+ position: absolute;
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ min-width: 350px;
+ max-width: 1200px;
+
+ .dashboard_navbar {
+ display: flex;
+ width: calc(100% - 20px);
+ height: 65px;
+ background: rgba(255, 255, 255, 0.2);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ border-bottom: 1px solid rgb(255, 255, 255);
+ box-shadow: 0 5px 4px rgba(0, 0, 0, 0.2);
+ flex-direction: row;
+ align-items: center;
+ padding: 0px 10px;
+
+ @media screen and (min-device-width:1200px) {
+ border-radius: 40px;
+
+ }
+
+ .menu_btn {
+ width: 30px;
+ height: 30px;
+ padding: 5px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 10px;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ border-radius: 10px;
+ margin-right: 10px;
+ background: rgba(0, 0, 0, 0.3);
+ display: none;
+
+ @media screen and (min-device-width: 350px) and (max-device-width: 650px) {
+ display: flex;
+ }
+ }
+
+ &.dark {
+ border-bottom: 1px solid rgb(136 136 136);
+ }
+
+ .theme_toggle {
+ display: flex;
+ align-items: center;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ border-radius: 10px;
+
+ // &.dark{
+ // background: rgba(255, 255, 255, 0.3);
+ // }
+ &.light {
+ background: rgba(0, 0, 0, 0.3);
+ }
+
+ div {
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &.dark {
+ svg {
+ width: 80%;
+
+ path {
+ fill: white;
+ }
+ }
+ }
+
+ &.light {
+ svg {
+ width: 100%;
+
+ path {
+ fill: #f1ff00;
+ }
+ }
+ }
+ }
+ }
+
+ .logo_image {
+ width: 40px;
+ height: 40px;
+ background: rgba(255, 255, 255, 0.68);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid #dadada;
+ outline: 1px solid #00000036;
+ backdrop-filter: blur(4px);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ margin-right: 10px;
+
+ img {
+ width: 25px;
+ }
+ }
+
+ .navbar_heading {
+ @include typography($heading3-size, 500, $default-line-height, #000000);
+
+ &.dark {
+ color: #dadada;
+ }
+ }
+
+ .navbar_void {
+ flex: 1;
+ }
+
+ .navbar_navlinks {
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ gap: 20px;
+ margin-right: 20px;
+
+ @media screen and (min-device-width: 350px) and (max-device-width: 650px) {
+ display: none;
+ // &.open {
+ // display: flex;
+ // position: absolute;
+ // z-index: 1;
+ // flex: 1;
+ // height: calc(100vh - 86px);
+ // }
+ }
+
+ .navbar_navlink_item {
+ font-weight: 400;
+ position: relative;
+
+ a {
+ text-decoration: none;
+ color: currentcolor;
+ }
+
+ &:hover {
+ color: $link_blue;
+ cursor: pointer;
+
+ &:before {
+ content: "";
+ width: 100%;
+ background-color: $link_blue;
+ height: 2px;
+ position: absolute;
+ border-radius: 2px;
+ bottom: -5px;
+ box-shadow: 0px 0px 14px;
+ }
+ }
+ }
+ }
+
+ .navbar_right {
+ width: 40px;
+ height: 40px;
+ background: rgba(255, 255, 255, 0.68);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(4px);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 50%;
+ border: 1px solid #dadada;
+ outline: 1px solid #00000036;
+ margin-right: 10px;
+
+ .profile_pic {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ img {
+ width: 100%;
+ border-radius: 50%;
+ }
+
+ svg {
+ width: 42px !important;
+ }
+ }
+ }
+ }
+
+ .dashboard_sidebar_and_contents {
+ width: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+
+ .dashboard_sidebar {
+ display: flex;
+ flex-direction: column;
+ width: 230px;
+ background: rgb(255 255 255 / 10%);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ padding: 10px;
+
+ @media screen and (min-device-width: 350px) and (max-device-width: 650px) {
+ display: none;
+
+ &.open {
+ display: flex;
+ position: absolute;
+ z-index: 1;
+ flex: 1;
+ height: calc(100vh - 86px);
+ }
+ }
+
+ @media screen and (min-device-width:1200px) {
+ background: unset;
+ -webkit-backdrop-filter: unset;
+ backdrop-filter: unset;
+ }
+
+ .dashboard_sidebar_contents {
+ width: 100%;
+ flex-grow: unset;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.45);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ overflow: auto;
+ padding: 2px 0;
+
+ &.dark {
+ background: rgb(0 0 0 / 54%);
+ outline: 1px solid #1f2025;
+ border: 1px solid #6d6d6d;
+ }
+
+ .sidebar_item_container {
+ display: flex;
+ flex-direction: column;
+ width: calc(100% - 5%);
+ align-items: center;
+
+ .sidebar_item {
+ display: flex;
+ flex-direction: column;
+ cursor: pointer;
+ width: calc(100% - 10px);
+ padding: 5px;
+ border-radius: 5px;
+ margin: 4px 0;
+ color: black;
+
+ a {
+ font-size: 16px;
+ font-weight: 400;
+ }
+
+ &:hover,
+ &.selected {
+ font-weight: 400;
+ background-color: rgb(255 255 255 / 26%);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ color: $link_blue;
+ }
+
+ &.dark {
+ color: $link_blue;
+ }
+ }
+
+ .horizontal_divider {
+ width: 100%;
+ height: 1px;
+ background: rgba(255, 255, 255, 0.19);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ }
+ }
+ }
+
+
+ }
+
+ .dashboard_sidebar_logoutbtn {
+ padding-top: 10px;
+ width: 60%;
+
+ .logoutBtn {
+ width: 100%;
+ border: none;
+ @include typography($subheading-size,
+ 500,
+ $default-line-height,
+ #fb7575);
+ border-radius: 10px;
+ }
+ }
+
+ .dashboard_contents_main_container {
+ flex: 1;
+ display: flex;
+ overflow: auto !important;
+ flex-direction: column;
+ padding: 10px;
+ row-gap: 10px;
+ position: relative !important;
+ // background: rgb(255 255 255 / 10%);
+ // -webkit-backdrop-filter: blur(10px);
+ // backdrop-filter: blur(10px);
+
+ .contents_header {
+ display: flex;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+
+ div.contents_header_container {
+ background: rgba(255, 255, 255, 0.1);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(5px);
+ box-shadow: rgb(40, 40, 40) 0px 4px 10px;
+ border-radius: 10px;
+ padding: 10px;
+ width: 100%;
+ height: 29.5px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+
+ &.dark {
+ background-color: unset;
+ outline: $dash_contents_dark_mode_outline;
+ border: $dash_contents_dark_mode_border;
+ }
+
+ h1 {
+ font-size: 24px;
+ margin: 0;
+ flex-grow: 1;
+ }
+ }
+
+ .addTodo_btn_div {
+ button {
+ background-color: green;
+ }
+ }
+ }
+
+ .contents_container {
+ border-radius: 10px;
+ row-gap: 10px;
+ flex: 1;
+ display: flex;
+
+ &.dark {}
+ }
+ }
+ }
+}
+
+.button_wrapper {
+ button {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .btn_text {
+ padding: 0 5px;
+ }
+
+ .add_icon {
+ margin-left: 5px;
+ border: 2px solid rgb(20 232 0 / 42%);
+
+ .line1,
+ .line2 {
+ background-color: rgb(110 255 78);
+ }
+ }
+ }
+}
+
+.addTodo_form_container {
+ form {
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+ width: 100%;
+
+ div {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+}
\ No newline at end of file
diff --git a/admin-client/src/components/WRAPPERS/DashboardWrapper/DashboardWrapper.tsx b/admin-client/src/components/WRAPPERS/DashboardWrapper/DashboardWrapper.tsx
new file mode 100644
index 0000000..ebd0e74
--- /dev/null
+++ b/admin-client/src/components/WRAPPERS/DashboardWrapper/DashboardWrapper.tsx
@@ -0,0 +1,315 @@
+import React, { ReactNode, useState } from "react";
+import "./DashboardWrapper.scss";
+import logo from "../../../medias/logo.png";
+// import { useNavigate } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { RootState } from "../../../ReduxStore/store";
+import { useDispatch } from "react-redux";
+import {
+ setLoading,
+ setSideBarActiveTab,
+ toggleMobSidebar,
+ toogleDarkLight,
+} from "../../../ReduxStore/UISlice";
+import Modal from "../../UIComponents/Modal/Modal";
+import AddIcon from "../../UIComponents/AddIcon/AddIcon";
+import { getUrl, includeDarkClass } from "../../../CONFIG";
+import { Link, Outlet, useNavigate, useParams } from "react-router-dom";
+import { MenuButton } from "../../UIComponents/MenuButton/MenuButton";
+import UserProfile from "../../../medias/UserProfile";
+
+interface DashboardWrapperProps {
+ handleLogout: any;
+ children?: ReactNode;
+ heading: string;
+ fetchAllUserData: any;
+}
+
+const lightModeSvgContent =
+ ' ';
+
+const darkModeSvgContent =
+ ' ';
+
+const sideBarData = [
+ { name: "Todos", url: "/todos" },
+ { name: null, url: null },
+ { name: "All Todos", url: "/all-todos" },
+ { name: "Profile", url: "/profile" },
+];
+const navLinks = [
+ { name: null, url: null },
+];
+
+const DashboardWrapper: React.FC = ({
+ children,
+ fetchAllUserData,
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [todoTitleInput, setTodoTitleInput] = useState(null);
+ const [todoDescInput, setTodoDescInput] = useState(null);
+ const [selectedStatus, setSelectedStatus] = useState('Todo');
+
+ const handleStatusChange = (event: React.ChangeEvent) => {
+ const newStatus = event.target.value;
+ setSelectedStatus(newStatus);
+ // onChange(newStatus);
+ };
+ const [selectedPriority, setSelectedPriority] = useState('Medium');
+
+ const handlePriorityChange = (event: React.ChangeEvent) => {
+ const newStatus = event.target.value;
+ setSelectedPriority(newStatus);
+ // onChange(newStatus);
+ };
+
+
+ const navigate = useNavigate();
+ const params = useParams();
+ const token = useSelector((state: RootState) => state && state.User && state.User.token);
+ const allUserData = useSelector((state: RootState) => state && state.User && state.User.allUserData);
+ const currentPage = useSelector((state: RootState) => state && state.UI && state.UI.currentPage);
+ const currSideBarIdx = useSelector((state: RootState) => state && state.UI && state.UI.sideBarActiveTab)
+ const theme = useSelector((state: RootState) => state && state.UI && state.UI.theme);
+ const darkMode = useSelector((state: RootState) => state && state.UI && state.UI.theme.dark);
+ const isMobSidebarOpen = useSelector(
+ (state: RootState) => state && state.UI && state.UI.isMobSidebarOpen
+ );
+ const isDarkMode = () => {
+ const localStorageDarkMode = localStorage && localStorage.getItem("darkMode");
+
+ if (
+ (localStorageDarkMode != null && localStorageDarkMode === "True") ||
+ (theme.dark && theme.dark === true)
+ ) {
+ return true;
+ }
+ return false;
+ };
+
+ const dispatch = useDispatch();
+
+ const handleAddTodo = async (event: React.FormEvent) => {
+ event.preventDefault();
+ dispatch(setLoading(true));
+ if (todoTitleInput && todoDescInput) {
+ const formData = new FormData();
+ formData.append("title", todoTitleInput ? todoTitleInput : 'Your title');
+ formData.append("description", todoDescInput ? todoDescInput : 'Your desc');
+ formData.append("status", selectedStatus ? selectedStatus : 'your status');
+ formData.append("priority", selectedPriority ? selectedPriority : 'priority');
+
+ try {
+ if (token !== null) {
+ const response = await fetch(getUrl("/admin/postTodo"), {
+ method: "POST",
+ body: formData,
+ headers: {
+ Authorization: token,
+ },
+ });
+ if (!response.ok) {
+ dispatch(setLoading(false));
+
+ setIsOpen(false);
+ throw new Error("Request failed");
+ }
+ // const jsonData = await response.json();
+ dispatch(setLoading(false));
+ setIsOpen(false);
+ dispatch(setLoading(true));
+ fetchAllUserData(token);
+ dispatch(setLoading(false));
+ // console.log(jsonData)
+ }
+ } catch (err) {
+ console.error("Error:", err);
+ dispatch(setLoading(false));
+ setIsOpen(false);
+ }
+ }
+ };
+ return (
+
+
+
+
+
+
+
+
+
+ Todos
+
+
+
+ {navLinks && navLinks.length !== 0 && navLinks.map((item, index) => {
+ return (
+
+ {item && item.url && item.name && {item.name}}
+
+ );
+ })}
+
+
+
navigate("/profile")}
+ className={includeDarkClass("profile_pic", darkMode)}
+ >
+ {allUserData && allUserData.picUrl ?
+
dispatch(setSideBarActiveTab(sideBarData && sideBarData.length && sideBarData.length - 1))} src={allUserData.picUrl} alt="profile pic" /> :
+
+ }
+
+
+
+ {isDarkMode() ? (
+
dispatch(toogleDarkLight())}
+ className={includeDarkClass(" ", darkMode)}
+ dangerouslySetInnerHTML={{ __html: darkModeSvgContent }}
+ >
+ ) : (
+
dispatch(toogleDarkLight())}
+ className={includeDarkClass(" ", darkMode)}
+ dangerouslySetInnerHTML={{ __html: lightModeSvgContent }}
+ >
+ )}
+
+
+
+
+
+ {sideBarData && sideBarData.length !== 0 && sideBarData.map((Item, index) => {
+ return (
+ <>{
+ Item && Item.name && Item.name !== null ?
dispatch(setSideBarActiveTab(index))}
+ >
+
+ {Item && Item.url && Item.name && dispatch(toggleMobSidebar())}
+ to={Item.url}
+ >
+ {Item.name}
+ }
+
+ {index !== sideBarData.length - 1 ?
: <>>}
+
: <>>}
+ >
+ );
+ })}
+
+
+
+
+
+
+ {!params.parentTodo_id ? (
+
+
setIsOpen(!isOpen)}>
+
+ Add todo
+
+
+
+
+ ) : (
+ <>>
+ )}
+
+
+
+ {children}
+ {/* FOR rendering the underlying children components */}
+
+
+
setIsOpen(!isOpen)}
+ >
+
+
+
+
+
+ );
+};
+
+export default DashboardWrapper;
diff --git a/admin-client/src/components/WRAPPERS/ModalWrapper/ModalWrapper.scss b/admin-client/src/components/WRAPPERS/ModalWrapper/ModalWrapper.scss
new file mode 100644
index 0000000..e69de29
diff --git a/admin-client/src/components/WRAPPERS/ModalWrapper/ModalWrapper.tsx b/admin-client/src/components/WRAPPERS/ModalWrapper/ModalWrapper.tsx
new file mode 100644
index 0000000..552def9
--- /dev/null
+++ b/admin-client/src/components/WRAPPERS/ModalWrapper/ModalWrapper.tsx
@@ -0,0 +1,23 @@
+import { ReactNode, useState } from "react"
+import Modal from "../../UIComponents/Modal/Modal"
+
+interface ModalWrapperProps {
+ heading: string,
+ children: ReactNode
+}
+
+const ModalWrapper: React.FC = ({ heading, children }) => {
+
+ const [isOpen, setIsOpen] = useState(false)
+
+ const handleToggle = () => {
+ setIsOpen(!isOpen)
+ }
+ return (
+
+ {children}
+
+ )
+}
+
+export default ModalWrapper;
\ No newline at end of file
diff --git a/admin-client/src/hooks/useGETAllTodos.tsx b/admin-client/src/hooks/useGETAllTodos.tsx
new file mode 100644
index 0000000..43e0655
--- /dev/null
+++ b/admin-client/src/hooks/useGETAllTodos.tsx
@@ -0,0 +1,49 @@
+import { useState, useEffect } from 'react';
+import { useDispatch } from 'react-redux';
+import { setAllTodos /* setLoading */ } from '../ReduxStore/UISlice';
+import { RootState } from '../ReduxStore/store';
+import { useSelector } from 'react-redux';
+
+type HttpError = {
+ message: string;
+ status?: number;
+};
+
+function useUserProfileCall(url: string, options: object): [HttpError | null] {
+ const [err, setErr] = useState(null);
+ const dispatch = useDispatch();
+
+ const token = useSelector((state: RootState) => state.User.token)
+
+ useEffect(() => {
+ const fetchData = async (token: string | null) => {
+ // dispatch(setLoading(true));
+ try {
+ if (token !== null) {
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ // dispatch(setLoading(false));
+ throw new Error('Request failed');
+ }
+ const jsonData = await response.json();
+ dispatch(setAllTodos(jsonData));
+ // dispatch(setLoading(false));
+ // console.log(jsonData)
+ }
+ } catch (err) {
+ console.error('Error:', err);
+ setErr({
+ message: (err as Error).message,
+ });
+ // dispatch(setLoading(false));
+ }
+ };
+
+ fetchData(token);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [dispatch, token]);
+
+ return [err];
+}
+
+export default useUserProfileCall;
diff --git a/admin-client/src/hooks/useHTTP.tsx b/admin-client/src/hooks/useHTTP.tsx
new file mode 100644
index 0000000..29d53cd
--- /dev/null
+++ b/admin-client/src/hooks/useHTTP.tsx
@@ -0,0 +1,60 @@
+import { useState, useEffect } from 'react';
+import { setLoading } from '../ReduxStore/UISlice';
+import { useDispatch } from 'react-redux';
+
+type HttpError = {
+ message: string;
+ status?: number;
+};
+
+function useHttp(
+ url: string,
+ options?: RequestInit
+): [T | null, HttpError | null] {
+ const [response, setResponse] = useState(null);
+ const [error, setError] = useState(null);
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ // dispatch(setLoading(true));
+ try {
+ const headers: HeadersInit = {};
+ if (options?.headers) {
+ for (const [key, value] of Object.entries(options.headers)) {
+ if (value !== null) {
+ headers[key] = value.toString();
+ }
+ }
+ }
+ const fetchOptions: RequestInit = {
+ ...options,
+ headers,
+ };
+
+ const fetchResponse = await fetch(url, fetchOptions);
+ if (!fetchResponse.ok) {
+ // dispatch(setLoading(false));
+ throw new Error(`Request failed with status: ${fetchResponse.status}`);
+ }
+
+ const responseData: T = await fetchResponse.json();
+ setResponse(responseData);
+ // dispatch(setLoading(false));
+ } catch (error: unknown) {
+ setError({
+ message: (error as Error).message,
+ status: (error as HttpError).status,
+ });
+ // dispatch(setLoading(false));
+ }
+ };
+
+ fetchData();
+ }, [url, options, dispatch]);
+
+ return [response, error];
+}
+
+export default useHttp;
diff --git a/admin-client/src/hooks/useUserProfileAPICall.tsx b/admin-client/src/hooks/useUserProfileAPICall.tsx
new file mode 100644
index 0000000..066465e
--- /dev/null
+++ b/admin-client/src/hooks/useUserProfileAPICall.tsx
@@ -0,0 +1,50 @@
+import { useState, useEffect } from 'react';
+import { useDispatch } from 'react-redux';
+// import { setLoading } from '../ReduxStore/UISlice';
+import { RootState } from '../ReduxStore/store';
+import { useSelector } from 'react-redux';
+import { setAllUserData } from '../ReduxStore/UserSlice';
+
+type HttpError = {
+ message: string;
+ status?: number;
+};
+
+function useUserProfileCall(url: string, options: object): [HttpError | null] {
+ const [err, setErr] = useState(null);
+ const dispatch = useDispatch();
+
+ const token = useSelector((state: RootState) => state.User.token)
+
+ useEffect(() => {
+ const fetchData = async (token: string | null) => {
+ // dispatch(setLoading(true));
+ try {
+ if (token !== null) {
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ // dispatch(setLoading(false));
+ throw new Error('Request failed');
+ }
+ const jsonData = await response.json();
+ dispatch(setAllUserData(jsonData));
+ // dispatch(setLoading(false));
+ // console.log(jsonData)
+ }
+ } catch (err) {
+ console.error('Error:', err);
+ setErr({
+ message: (err as Error).message,
+ });
+ // dispatch(setLoading(false));
+ }
+ };
+
+ fetchData(token);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [dispatch, token]);
+
+ return [err];
+}
+
+export default useUserProfileCall;
diff --git a/admin-client/src/index.css b/admin-client/src/index.css
new file mode 100644
index 0000000..ec2585e
--- /dev/null
+++ b/admin-client/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/admin-client/src/index.tsx b/admin-client/src/index.tsx
new file mode 100644
index 0000000..e9a7895
--- /dev/null
+++ b/admin-client/src/index.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+import { Provider } from 'react-redux';
+import store from './ReduxStore/store';
+import { BrowserRouter } from "react-router-dom";
+
+const ReduxProvider = Provider
+
+ReactDOM.render(
+
+
+
+
+
+
+
+ , document.getElementById('root'));
+
+// Before
+// ReactDOM.render( , document.getElementById('root'));
+
+// After
+// createRoot(document.getElementById('root')).render( );
\ No newline at end of file
diff --git a/admin-client/src/medias/404 Error (1).gif b/admin-client/src/medias/404 Error (1).gif
new file mode 100644
index 0000000..e3479a7
Binary files /dev/null and b/admin-client/src/medias/404 Error (1).gif differ
diff --git a/admin-client/src/medias/404 Error.gif b/admin-client/src/medias/404 Error.gif
new file mode 100644
index 0000000..09ed376
Binary files /dev/null and b/admin-client/src/medias/404 Error.gif differ
diff --git a/admin-client/src/medias/ChevronRight copy.jsx b/admin-client/src/medias/ChevronRight copy.jsx
new file mode 100644
index 0000000..f0c0c56
--- /dev/null
+++ b/admin-client/src/medias/ChevronRight copy.jsx
@@ -0,0 +1,8 @@
+const Nothing = () => {
+ return (
+
+ )
+}
+
+export default Nothing;
\ No newline at end of file
diff --git a/admin-client/src/medias/ChevronRight.jsx b/admin-client/src/medias/ChevronRight.jsx
new file mode 100644
index 0000000..d6f5f98
--- /dev/null
+++ b/admin-client/src/medias/ChevronRight.jsx
@@ -0,0 +1,7 @@
+const ChevronRight = () => {
+ return (
+
+ )
+}
+
+export default ChevronRight;
\ No newline at end of file
diff --git a/admin-client/src/medias/CrossIcon.jsx b/admin-client/src/medias/CrossIcon.jsx
new file mode 100644
index 0000000..3d6d495
--- /dev/null
+++ b/admin-client/src/medias/CrossIcon.jsx
@@ -0,0 +1,11 @@
+const CrossIcon = () => {
+ return (
+
+ )
+}
+
+export default CrossIcon;
\ No newline at end of file
diff --git a/admin-client/src/medias/EditUserProfile.jsx b/admin-client/src/medias/EditUserProfile.jsx
new file mode 100644
index 0000000..c36ee33
--- /dev/null
+++ b/admin-client/src/medias/EditUserProfile.jsx
@@ -0,0 +1,7 @@
+const EditUserProfile = () => {
+ return (
+
+ )
+}
+
+export default EditUserProfile
\ No newline at end of file
diff --git a/admin-client/src/medias/Editsvg.jsx b/admin-client/src/medias/Editsvg.jsx
new file mode 100644
index 0000000..0a85199
--- /dev/null
+++ b/admin-client/src/medias/Editsvg.jsx
@@ -0,0 +1,7 @@
+const Editsvg = () => {
+ return (
+
+ )
+}
+
+export default Editsvg;
\ No newline at end of file
diff --git a/admin-client/src/medias/UserProfile.jsx b/admin-client/src/medias/UserProfile.jsx
new file mode 100644
index 0000000..c216d22
--- /dev/null
+++ b/admin-client/src/medias/UserProfile.jsx
@@ -0,0 +1,12 @@
+const UserProfile = () => {
+ return
+
+
+
+
+
+
+
+}
+
+export default UserProfile;
\ No newline at end of file
diff --git a/admin-client/src/medias/add.png b/admin-client/src/medias/add.png
new file mode 100644
index 0000000..6f5a6c8
Binary files /dev/null and b/admin-client/src/medias/add.png differ
diff --git a/admin-client/src/medias/bgfinaldark.jpg b/admin-client/src/medias/bgfinaldark.jpg
new file mode 100644
index 0000000..6e0ef2d
Binary files /dev/null and b/admin-client/src/medias/bgfinaldark.jpg differ
diff --git a/admin-client/src/medias/bgfinallight.jpg b/admin-client/src/medias/bgfinallight.jpg
new file mode 100644
index 0000000..49da791
Binary files /dev/null and b/admin-client/src/medias/bgfinallight.jpg differ
diff --git a/admin-client/src/medias/child-todos.svg b/admin-client/src/medias/child-todos.svg
new file mode 100644
index 0000000..8884c44
--- /dev/null
+++ b/admin-client/src/medias/child-todos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/admin-client/src/medias/hidden.png b/admin-client/src/medias/hidden.png
new file mode 100644
index 0000000..7d71841
Binary files /dev/null and b/admin-client/src/medias/hidden.png differ
diff --git a/admin-client/src/medias/icons8-edit-512.svg b/admin-client/src/medias/icons8-edit-512.svg
new file mode 100644
index 0000000..f5a10aa
--- /dev/null
+++ b/admin-client/src/medias/icons8-edit-512.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/admin-client/src/medias/icons8-edit.gif b/admin-client/src/medias/icons8-edit.gif
new file mode 100644
index 0000000..476ae47
Binary files /dev/null and b/admin-client/src/medias/icons8-edit.gif differ
diff --git a/admin-client/src/medias/index.js b/admin-client/src/medias/index.js
new file mode 100644
index 0000000..ffc1dda
--- /dev/null
+++ b/admin-client/src/medias/index.js
@@ -0,0 +1,10 @@
+import AddImg from "./add.png"
+import DeleteImg from "./remove.png"
+import hide from "./hidden.png"
+import show from "./view.png"
+import rightArrow from "./rightarrow.svg"
+import edit from "./icons8-edit-512.svg"
+import childTodo from "./subtask.svg"
+
+
+export { AddImg , DeleteImg , hide ,show, rightArrow,edit,childTodo}
\ No newline at end of file
diff --git a/admin-client/src/medias/light.svg b/admin-client/src/medias/light.svg
new file mode 100644
index 0000000..1ae3e1f
--- /dev/null
+++ b/admin-client/src/medias/light.svg
@@ -0,0 +1 @@
+Created by Annisa Aulia Rahman from the Noun Project
\ No newline at end of file
diff --git a/admin-client/src/medias/logo.png b/admin-client/src/medias/logo.png
new file mode 100644
index 0000000..3d79fd0
Binary files /dev/null and b/admin-client/src/medias/logo.png differ
diff --git a/admin-client/src/medias/noun-moon-4951761.svg b/admin-client/src/medias/noun-moon-4951761.svg
new file mode 100644
index 0000000..cb41bf2
--- /dev/null
+++ b/admin-client/src/medias/noun-moon-4951761.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/admin-client/src/medias/remove.png b/admin-client/src/medias/remove.png
new file mode 100644
index 0000000..2b4c356
Binary files /dev/null and b/admin-client/src/medias/remove.png differ
diff --git a/admin-client/src/medias/right-arrow.png b/admin-client/src/medias/right-arrow.png
new file mode 100644
index 0000000..d3fc6c6
Binary files /dev/null and b/admin-client/src/medias/right-arrow.png differ
diff --git a/admin-client/src/medias/rightarrow.svg b/admin-client/src/medias/rightarrow.svg
new file mode 100644
index 0000000..6a8533d
--- /dev/null
+++ b/admin-client/src/medias/rightarrow.svg
@@ -0,0 +1 @@
+92-Arrow Right
\ No newline at end of file
diff --git a/admin-client/src/medias/subtask.svg b/admin-client/src/medias/subtask.svg
new file mode 100644
index 0000000..ce93af6
--- /dev/null
+++ b/admin-client/src/medias/subtask.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/admin-client/src/medias/user.svg b/admin-client/src/medias/user.svg
new file mode 100644
index 0000000..266c95b
--- /dev/null
+++ b/admin-client/src/medias/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/admin-client/src/medias/view.png b/admin-client/src/medias/view.png
new file mode 100644
index 0000000..2810628
Binary files /dev/null and b/admin-client/src/medias/view.png differ
diff --git a/admin-client/src/react-app-env.d.ts b/admin-client/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/admin-client/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/admin-client/src/reportWebVitals.ts b/admin-client/src/reportWebVitals.ts
new file mode 100644
index 0000000..49a2a16
--- /dev/null
+++ b/admin-client/src/reportWebVitals.ts
@@ -0,0 +1,15 @@
+import { ReportHandler } from 'web-vitals';
+
+const reportWebVitals = (onPerfEntry?: ReportHandler) => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/admin-client/src/setupTests.ts b/admin-client/src/setupTests.ts
new file mode 100644
index 0000000..8f2609b
--- /dev/null
+++ b/admin-client/src/setupTests.ts
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';
diff --git a/admin-client/src/styles/typography/typography.scss b/admin-client/src/styles/typography/typography.scss
new file mode 100644
index 0000000..18f2dc1
--- /dev/null
+++ b/admin-client/src/styles/typography/typography.scss
@@ -0,0 +1,75 @@
+// _typography.scss
+
+@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Signika:wght@300;400;500;600;700&display=swap');
+
+// Font families
+$font-primary: 'Helvetica Neue', Arial, sans-serif;
+$font-secondary: 'Roboto', sans-serif;
+
+// Heading styles
+$heading1-size: 2.5rem;
+$heading2-size: 2rem;
+$heading3-size: 1.75rem;
+$heading4-size: 1.5rem;
+$heading5-size: 1.25rem;
+$heading6-size: 1rem;
+
+$heading1-font: $font-primary;
+$heading2-font: $font-primary;
+$heading3-font: $font-primary;
+$heading4-font: $font-primary;
+$heading5-font: $font-primary;
+$heading6-font: $font-primary;
+
+$heading1-line-height: 1.2;
+$heading2-line-height: 1.3;
+$heading3-line-height: 1.4;
+$heading4-line-height: 1.5;
+$heading5-line-height: 1.6;
+$heading6-line-height: 1.7;
+
+$heading1-font-weight: 700;
+$heading2-font-weight: 600;
+$heading3-font-weight: 600;
+$heading4-font-weight: 600;
+$heading5-font-weight: 600;
+$heading6-font-weight: 600;
+
+$heading1-color: #333;
+$heading2-color: #333;
+$heading3-color: #333;
+$heading4-color: #333;
+$heading5-color: #333;
+$heading6-color: #333;
+
+// Subheading styles
+$subheading-size: 1.1rem;
+$subheading-font: $font-secondary;
+$subheading-line-height: 1.4;
+$subheading-font-weight: 600;
+$subheading-color: #666;
+
+// Body text styles
+$body-size: 1rem;
+$body-font: $font-primary;
+$body-line-height: 1.6;
+$body-font-weight: 400;
+$body-color: #444;
+
+
+$default-font-size: 1rem;
+$default-font-weight: 300;
+$default-line-height: 1.6;
+
+$link_blue:#0080ff;
+
+$dark_mode_outline:1px solid #1f2025;
+$dark_mode_border:1px solid #6d6d6d;
+$dark_mode_bgColor:rgba(0, 0, 0, 0.54);
+
+@mixin typography($font-size: $default-font-size, $font-weight: $default-font-weight, $line-height: $default-line-height, $color: inherit) {
+ font-size: $font-size;
+ font-weight: $font-weight;
+ line-height: $line-height;
+ color: $color;
+}
diff --git a/admin-client/src/utilFuncs/getRandomColor.tsx b/admin-client/src/utilFuncs/getRandomColor.tsx
new file mode 100644
index 0000000..2052175
--- /dev/null
+++ b/admin-client/src/utilFuncs/getRandomColor.tsx
@@ -0,0 +1,9 @@
+function getRandomColor(): string {
+ const randomValue = () => Math.floor(Math.random() * 156) + 100; // Range: 100-255
+ const r = randomValue();
+ const g = randomValue();
+ const b = randomValue();
+ return `rgb(${r}, ${g}, ${b})`;
+}
+
+export default getRandomColor
\ No newline at end of file
diff --git a/admin-client/tsconfig.json b/admin-client/tsconfig.json
new file mode 100644
index 0000000..c787c37
--- /dev/null
+++ b/admin-client/tsconfig.json
@@ -0,0 +1,32 @@
+
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "es6",
+ "esnext"
+ ],
+ "allowJs": true,
+ "downlevelIteration": true,
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "strictNullChecks": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "src"
+ ]
+}