├── src
├── hooks
│ ├── index.tsx
│ └── useToogle.tsx
├── react-app-env.d.ts
├── utils
│ ├── index.ts
│ ├── dateFormat.ts
│ └── validation.ts
├── containers
│ ├── Tasks
│ │ └── index.tsx
│ ├── index.tsx
│ ├── Auth
│ │ └── Login
│ │ │ ├── index.tsx
│ │ │ ├── SignIn.tsx
│ │ │ └── SignUp.tsx
│ ├── Home
│ │ └── index.tsx
│ └── Users
│ │ └── index.tsx
├── components
│ ├── index.tsx
│ ├── Footer.tsx
│ ├── styled
│ │ ├── ValidationErrorText.ts
│ │ ├── index.ts
│ │ ├── Form.ts
│ │ ├── Select.ts
│ │ ├── FormItem.ts
│ │ ├── ScrolledArea.ts
│ │ ├── Layout.ts
│ │ ├── Input.ts
│ │ ├── Button.ts
│ │ └── Card.ts
│ ├── PrivateRoute.tsx
│ ├── Loading.tsx
│ └── Header.tsx
├── setupTests.ts
├── theme
│ ├── my-theme.ts
│ └── styled.d.ts
├── assets
│ └── icons
│ │ └── plus-circle.svg
├── App.tsx
├── reportWebVitals.ts
├── client
│ └── index.tsx
├── models.ts
├── store
│ ├── index.tsx
│ ├── users
│ │ ├── api_action.tsx
│ │ └── index.tsx
│ ├── auth
│ │ ├── api_action.tsx
│ │ └── index.tsx
│ └── loadings
│ │ └── index.tsx
├── index.tsx
└── router
│ └── index.tsx
├── .prettierrc
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── .idea
├── .gitignore
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── vcs.xml
├── modules.xml
├── smart-solutions-task.iml
└── inspectionProfiles
│ └── Project_Default.xml
├── .gitignore
├── routes
├── static-mocks.js
└── item.js
├── tsconfig.json
├── server
└── server.js
├── .eslintrc.json
├── README.md
└── package.json
/src/hooks/index.tsx:
--------------------------------------------------------------------------------
1 | import { useToggle } from './useToogle';
2 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "es5"
4 | }
5 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmilAbdullazadeh/management-system-task/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmilAbdullazadeh/management-system-task/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmilAbdullazadeh/management-system-task/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export { formatDate } from './dateFormat';
2 | export { validations, validateForm } from './validation';
3 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/src/containers/Tasks/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Tasks = () => {
4 | return (
5 |
Tasks
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { Loading } from './Loading';
2 | export { HeaderComponent } from './Header';
3 | export { FooterComponent } from './Footer';
4 |
5 |
--------------------------------------------------------------------------------
/src/containers/index.tsx:
--------------------------------------------------------------------------------
1 | export { Home } from './Home';
2 | export { Login } from './Auth/Login/';
3 | export { Users } from './Users';
4 | export { Tasks } from './Tasks';
5 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Layout } from 'antd';
3 |
4 | const { Footer } = Layout;
5 |
6 | export const FooterComponent: React.FC = () => {
7 | return (
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/src/hooks/useToogle.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | export const useToggle = (active = false) => {
4 | const [isActive, setActive] = useState(active);
5 |
6 | const toggle = () => {
7 | setActive(!isActive);
8 | };
9 | return [isActive, toggle];
10 | };
11 |
--------------------------------------------------------------------------------
/src/theme/my-theme.ts:
--------------------------------------------------------------------------------
1 | import {DefaultTheme} from 'styled-components'
2 |
3 | const myTheme: DefaultTheme = {
4 | colors: {
5 | main: "#fff",
6 | secondary: "#DF3B4C",
7 | white: "#fff",
8 | dark: "#16161D",
9 | lightGray: '#d2d2d2'
10 | }
11 | }
12 |
13 | export {myTheme}
14 |
--------------------------------------------------------------------------------
/src/theme/styled.d.ts:
--------------------------------------------------------------------------------
1 | import 'styled-components';
2 |
3 | declare module 'styled-components' {
4 | export interface DefaultTheme {
5 | colors: {
6 | main: string;
7 | secondary: string;
8 | white: string;
9 | dark: string;
10 | lightGray: string;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/utils/dateFormat.ts:
--------------------------------------------------------------------------------
1 | const padTo2Digits = (num: number) => {
2 | return num.toString().padStart(2, '0');
3 | };
4 |
5 | export const formatDate = (date: any) => {
6 | return (
7 | [
8 | padTo2Digits(date.getMonth() + 1),
9 | padTo2Digits(date.getDate()),
10 | date.getFullYear(),
11 | ].join('-')
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/src/assets/icons/plus-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/styled/ValidationErrorText.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface IProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const ValidationErrorText = styled.span`
9 | color: ${props => props.theme.colors.secondary};
10 | font-size: 14px;
11 | margin-top: 10px;
12 | `;
13 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Router} from './router'
3 | import {Layout, ScrolledArea} from './components/styled';
4 |
5 | const App: React.FC = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default App;
16 |
--------------------------------------------------------------------------------
/src/components/styled/index.ts:
--------------------------------------------------------------------------------
1 | export {Layout} from './Layout';
2 | export {Card, CardWrapper} from './Card';
3 | export {ScrolledArea} from './ScrolledArea';
4 | export {Input} from './Input';
5 | export {FormItem} from './FormItem';
6 | export {Select} from './Select';
7 | export {Form} from './Form';
8 | export {Button, SecondButton} from './Button';
9 | export {ValidationErrorText} from './ValidationErrorText';
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/components/PrivateRoute.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate, RouteProps, Route } from 'react-router-dom';
3 |
4 |
5 | type PrivateRouteProps = RouteProps & {
6 | children: any;
7 | }
8 |
9 | function PrivateRoute({ children }: PrivateRouteProps) {
10 | const auth = localStorage.getItem('userData');
11 | return auth ? children : ;
12 | }
13 |
14 | export default PrivateRoute;
15 |
--------------------------------------------------------------------------------
/src/components/styled/Form.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface FormProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const Form = styled.div`
9 | padding-top: 10px;
10 | margin-top: 10px;
11 | border-radius: 5px;
12 | width: 800px;
13 | display: flex;
14 | flex-direction: row;
15 | flex-wrap: wrap;
16 | border: none;
17 | outline: none;
18 | `;
19 |
--------------------------------------------------------------------------------
/src/components/styled/Select.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface SelectProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const Select = styled.select`
9 | padding: 10px;
10 | font-size: 14px;
11 | border-radius: 10px;
12 | width: 100%;
13 | border: solid 1px ${props => props.theme.colors.lightGray};
14 | outline: none;
15 | position: relative;
16 | `;
17 |
18 |
--------------------------------------------------------------------------------
/src/components/styled/FormItem.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface FormItemProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const FormItem = styled.div`
9 | display: flex;
10 | flex-direction: column;
11 | background-color: transparent;
12 | margin: 20px 20px 20px 0;
13 | width: 300px;
14 |
15 | label {
16 | font-size: 14px;
17 | margin-bottom: 12px;
18 | }
19 | `;
20 |
21 |
--------------------------------------------------------------------------------
/src/components/styled/ScrolledArea.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface ScrolledAreaProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const ScrolledArea = styled.div`
9 | overflow-y: auto;
10 | overflow-x: hidden;
11 | height: 100%;
12 | display: flex;
13 | flex-wrap: wrap;
14 | align-content: baseline;
15 | padding: 10px 20px 30px 35px
16 | /* justify-content: center; */
17 | `;
18 |
19 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/routes/static-mocks.js:
--------------------------------------------------------------------------------
1 | const statuses = [
2 | "To do",
3 | "In progress",
4 | "Done"
5 | ]
6 |
7 | // const products = [
8 | // {
9 | // productName: "Çörəkdə ət dönər",
10 | // amount: 7
11 | // },
12 | // {
13 | // productName: "Toyuq kotlet",
14 | // amount: 3
15 | // },
16 | // {
17 | // productName: "Lavaşda toyuq dönər",
18 | // amount: 5.5
19 | // }
20 | // ]
21 |
22 | module.exports = {
23 | statuses,
24 | };
25 |
--------------------------------------------------------------------------------
/.idea/smart-solutions-task.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/styled/Layout.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface LayoutProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const Layout = styled.div`
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: center;
12 | min-height: 100vh;
13 | min-width: 100%;
14 | background-color: ${props => props.theme.colors.main};
15 | font-family: -apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;
16 | `;
17 |
18 |
--------------------------------------------------------------------------------
/src/containers/Auth/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Tabs } from 'antd';
3 |
4 | import { SignIn } from './SignIn';
5 | import { SignUp } from './SignUp';
6 |
7 | const onChange = (key: string) => {
8 | };
9 |
10 | export const Login: React.FC = () => (
11 | ,
19 | },
20 | {
21 | label: `Sign up`,
22 | key: '2',
23 | children: ,
24 | },
25 | ]}
26 | />
27 | );
28 |
--------------------------------------------------------------------------------
/src/client/index.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const headers: any = {'content-type': 'application/json'};
4 | const baseURL: string = 'http://localhost:4000/';
5 |
6 | const client = (endpoint: string, body?: any, method?: string, {...customConfig}: any = {}) => {
7 | const config = {
8 | baseURL,
9 | url: endpoint,
10 | data: body,
11 | method: method ? method : body ? "POST" : "GET",
12 | ...customConfig,
13 | headers: {
14 | ...headers,
15 | ...customConfig.headers
16 | }
17 | }
18 |
19 | return axios.request(config).then(res => res?.data)
20 | }
21 |
22 | export default client;
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/models.ts:
--------------------------------------------------------------------------------
1 | export interface IOrganizations {
2 | id: string
3 | orginzationName: string
4 | phoneNumber: string
5 | address: string
6 | userName: string
7 | email: string
8 | password: string
9 | isAdmin: boolean
10 | users: IUsers[] | []
11 | [x:string]: any;
12 | }
13 |
14 | export interface IUsers {
15 | id: string
16 | name: string
17 | surname: string
18 | email: string
19 | password: string
20 | tasks: ITasks[]
21 | }
22 |
23 | export interface ITasks{
24 | id: string
25 | title: string
26 | description: string
27 | deadline: string
28 | status: string[]
29 | // users: IUsers[]
30 | }
31 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const express = require('express');
3 | const cors = require('cors');
4 |
5 | // import `items` from `routes` folder
6 | const {createUser, getUsers, getData, getFindedData, login, register } = require('../routes/item')
7 | // create new app
8 | const app = express();
9 | // for cors error
10 | app.use(cors());
11 | app.use(express.json());
12 |
13 | // app.get('/', getData);
14 | // app.get('/:id', getFindedData);
15 | app.post('/login', login);
16 | app.post('/register', register);
17 | app.get('/users', getUsers);
18 | app.post('/users', createUser);
19 |
20 | const server = http.createServer(app);
21 | const port = 4000;
22 | server.listen(port);
23 | console.debug('Server listening on port ' + port);
24 |
--------------------------------------------------------------------------------
/src/components/styled/Input.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface InputProps {
5 | theme: DefaultTheme;
6 | type: string
7 | }
8 |
9 | export const Input = styled.input`
10 | display: flex;
11 | align-items: center;
12 | padding: 10px;
13 | font-size: 14px;
14 | border-radius: 10px;
15 | max-width: ${props => props.type === 'number' ? '35px' : '100%'};
16 | border: solid 1px ${props => props.theme.colors.lightGray};
17 | outline: none;
18 | text-align: ${props => props.type === 'number' ? 'center' : 'left'};;
19 | background-color: transparent;
20 |
21 | ::-webkit-outer-spin-button,
22 | ::-webkit-inner-spin-button {
23 | -webkit-appearance: none;
24 | margin: 0;
25 | }
26 | `;
27 |
28 |
--------------------------------------------------------------------------------
/src/store/index.tsx:
--------------------------------------------------------------------------------
1 | import {configureStore} from '@reduxjs/toolkit';
2 | import loadingsReducer from "./loadings";
3 | import {userRegisterReducer, userLoginReducer} from './auth';
4 | import { userDataReducer } from './users';
5 | import {IUsers, ITasks, IOrganizations} from "../models";
6 |
7 | export default configureStore({
8 | devTools: process.env.NODE_ENV !== 'production',
9 | reducer: {
10 | loadings: loadingsReducer,
11 | register: userRegisterReducer,
12 | login: userLoginReducer,
13 | users: userDataReducer
14 | }
15 | })
16 |
17 | export interface RootState {
18 | loadings: any,
19 | register: { newUser: IOrganizations, error: Object | string },
20 | login: { userData: IOrganizations, error: Object | string },
21 | users: { userData: IUsers[], error: Object | string },
22 | }
23 |
--------------------------------------------------------------------------------
/src/store/users/api_action.tsx:
--------------------------------------------------------------------------------
1 | import {createAsyncThunk} from '@reduxjs/toolkit';
2 | import client from '../../client';
3 |
4 | export const UserData = createAsyncThunk(
5 | 'userData',
6 | async (config, thunkAPI) => {
7 | try {
8 | const data = await client('users')
9 | localStorage.setItem('AllUsers', JSON.stringify(data))
10 | return data
11 | } catch (error) {
12 | // @ts-ignore
13 | return thunkAPI.rejectWithValue(error.response)
14 | }
15 | })
16 |
17 | export const UserCreate = createAsyncThunk(
18 | 'userCreate',
19 | async (config, thunkAPI) => {
20 | // @ts-ignore
21 | try {
22 | const data = await client(`users`, config)
23 | return data
24 | } catch (error) {
25 | // @ts-ignore
26 | return thunkAPI.rejectWithValue(error.response)
27 | }
28 | })
29 |
--------------------------------------------------------------------------------
/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import styled from 'styled-components';
3 | import { Spin } from 'antd';
4 |
5 | interface IProps {
6 | loading?: boolean,
7 | isFull?: boolean
8 | }
9 |
10 | export const Loading: React.FC = ({ loading, isFull }) => {
11 | return (
12 | loading ?
13 |
14 |
15 |
16 | :
17 |
18 |
19 | );
20 | };
21 |
22 | const LoadingContainer = styled.div`
23 | width: 100%;
24 | height: 100%;
25 | position: fixed;
26 | right: 0;
27 | bottom: 0;
28 | z-index: 99999;
29 | overflow: hidden;
30 | background: rgba(255, 255, 255, 0.9);
31 | //background: transparent;
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | `;
36 |
--------------------------------------------------------------------------------
/src/store/auth/api_action.tsx:
--------------------------------------------------------------------------------
1 | import {createAsyncThunk} from '@reduxjs/toolkit';
2 | import client from '../../client';
3 |
4 | export const UserRegister = createAsyncThunk('userRegister', async (config, thunkAPI) => {
5 | try {
6 | const data = await client('/register', config);
7 | localStorage.setItem('newUser', JSON.stringify(data));
8 | return data;
9 | } catch (error) {
10 | // @ts-ignore
11 | return thunkAPI.rejectWithValue(error.response);
12 | }
13 | });
14 |
15 | export const UserLogin = createAsyncThunk(
16 | 'userLogin',
17 | async (config, thunkAPI) => {
18 | try {
19 | const data = await client('/login', config);
20 | localStorage.setItem('userData', JSON.stringify(data));
21 | return data;
22 | } catch (error) {
23 | // @ts-ignore
24 | return thunkAPI.rejectWithValue(error.response);
25 | }
26 | }
27 | );
28 |
--------------------------------------------------------------------------------
/src/components/styled/Button.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface ButtonProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const Button = styled.button`
9 | background-color: ${props => props.theme.colors.secondary};
10 | border: none;
11 | color: white;
12 | padding: 15px 32px;
13 | text-align: center;
14 | font-size: 16px;
15 | margin-right: 20px;
16 | margin-top: 30px;
17 | cursor: pointer;
18 | width: 165px;
19 | border-radius: 4px;
20 | transition: all .35s linear;
21 | `;
22 |
23 | export const SecondButton = styled.button`
24 | background-color: ${props => props.theme.colors.main};
25 | border: none;
26 | color: ${props => props.theme.colors.dark};
27 | padding: 15px 32px;
28 | text-align: center;
29 | font-size: 16px;
30 | margin-right: 20px;
31 | cursor: pointer;
32 | `
33 |
--------------------------------------------------------------------------------
/src/components/styled/Card.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import {DefaultTheme} from "styled-components";
3 |
4 | interface CardProps {
5 | theme: DefaultTheme;
6 | }
7 |
8 | export const Card = styled.div`
9 | margin: 15px;
10 | text-align: center;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | font-weight: bold;
15 | height: 200px;
16 | width: 250px;
17 | border-radius: 5px;
18 | background-color: ${props => props.theme.colors.white};
19 | cursor: pointer;
20 | transition: all 0.2s;
21 |
22 | &:hover {
23 | background-color: ${props => props.theme.colors.secondary};
24 | color: ${props => props.theme.colors.white}
25 | }
26 | `;
27 |
28 | export const CardWrapper = styled.div`
29 | width: 100%;
30 | height: auto;
31 | background-color: ${props => props.theme.colors.white};
32 | padding: 40px;
33 | `
34 |
35 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "plugin:react/recommended",
8 | "xo"
9 | ],
10 | "overrides": [
11 | {
12 | "extends": [
13 | "xo-typescript",
14 | "plugin:prettier/recommended"
15 | ],
16 | "files": [
17 | "*.ts",
18 | "*.tsx"
19 | ]
20 | }
21 | ],
22 | "parserOptions": {
23 | "ecmaVersion": "latest",
24 | "sourceType": "module"
25 | },
26 | "plugins": [
27 | "react"
28 | ],
29 | "rules": {
30 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
31 | "react/jsx-filename-extension": [1, {
32 | "extensions": [".ts", ".tsx"]}
33 | ],
34 | rules: {
35 | 'prettier/prettier': 0,
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/containers/Home/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Breadcrumb, Layout, Menu, theme } from 'antd';
4 |
5 | import { HeaderComponent, FooterComponent} from '../../components';
6 | import {Users, Tasks} from '../index'
7 |
8 | const { Content } = Layout;
9 | // import {Info} from '../components';
10 | // import {Orders} from '../containers'
11 |
12 | interface IProps {
13 | }
14 |
15 | export const Home: React.FC = () => {
16 | const {
17 | token: { colorBgContainer },
18 | } = theme.useToken();
19 |
20 | return (
21 |
22 |
23 |
24 | {/**/}
25 | {/**/}
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | const StyledLayout = styled(Layout)`
33 | min-width: 1200px;
34 | `;
35 |
36 | const StyledContent = styled(Content)`
37 | height: 100vh;
38 | padding: 0 50px;
39 | `;
40 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {createRoot} from 'react-dom/client';
3 | import {Provider} from 'react-redux';
4 | import {ThemeProvider} from 'styled-components';
5 | import {BrowserRouter} from 'react-router-dom';
6 |
7 | import {myTheme} from './theme/my-theme';
8 | import store from './store';
9 | import App from './App';
10 | import reportWebVitals from './reportWebVitals';
11 |
12 | const container = document.getElementById('root')!;
13 | const root = createRoot(container);
14 |
15 | root.render(
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 |
27 | // If you want to start measuring performance in your app, pass a function
28 | // to log results (for example: reportWebVitals(console.log))
29 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
30 | reportWebVitals();
31 |
--------------------------------------------------------------------------------
/src/utils/validation.ts:
--------------------------------------------------------------------------------
1 | export const validations = (values: any) => {
2 | let errors: any = {};
3 |
4 | Object.keys(values).map(item => {
5 | if (!values[item]) {
6 | return errors[item] = 'Field is required';
7 | }
8 | return true;
9 | });
10 |
11 | return errors;
12 | };
13 |
14 |
15 | export const validateForm = (inputs: any) => {
16 | //check for null values before validation
17 | for (let i = 0; i < inputs.length; i++) {
18 | const input = inputs[i];
19 | if (input.value === null) {
20 | alert('All fields are required!');
21 | return false;
22 | } else {
23 | switch (input.type) {
24 | case 'email':
25 | const emailRegEx = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
26 | if (!emailRegEx.test(input.value)) {
27 | alert('Please enter a valid email address!');
28 | return false;
29 | }
30 | break;
31 | case 'text':
32 | const regex = /^[a-zA-Z ]*$/;
33 | if (!regex.test(input.value)) {
34 | alert('Please enter valid characters!');
35 | return false;
36 | }
37 | }
38 | }
39 | }
40 | return true;
41 | };
42 |
43 |
--------------------------------------------------------------------------------
/src/store/loadings/index.tsx:
--------------------------------------------------------------------------------
1 | const loadingsReducer = (state = {}, action: any) => {
2 | const matches = /(.*)\/(pending|fulfilled|rejected)$/.exec(action.type);
3 | // if action types are not an api actions ( ***/pending|fulfilled|rejected ), so we ignore them
4 | if (!matches) return state;
5 |
6 | const [, requestName, requestState] = matches;
7 |
8 | return {
9 | ...state,
10 | // will be true when receiving fetchOrders/pending
11 | // and false when receiving fetchOrders/fulfilled or fetchAllOrders/rejected
12 | isLoading: requestState === "pending", // shared loading for most use cases
13 | [requestName]: requestState === "pending"
14 | };
15 | };
16 |
17 | export default loadingsReducer;
18 |
19 | // [fetchOrderById.pending]: (state, action) => {
20 | // if (state.loading === 'idle') {
21 | // state.loading = 'pending'
22 | // state.currentRequestId = action.meta.requestId
23 | // }
24 | // },
25 | // [fetchOrderById.fulfilled]: (state, action) => {
26 | // const { requestId } = action.meta
27 | // if (state.loading === 'pending' && state.currentRequestId === requestId) {
28 | // state.loading = 'idle'
29 | // state.entities.push(action.payload)
30 | // state.currentRequestId = undefined
31 | // }
32 | // },
33 | // [fetchOrderById.rejected]: (state, action) => {
34 | // const { requestId } = action.meta
35 | // if (state.loading === 'pending' && state.currentRequestId === requestId) {
36 | // state.loading = 'idle'
37 | // state.error = action.error
38 | // state.currentRequestId = undefined
39 | // }
40 | // }
41 |
--------------------------------------------------------------------------------
/src/store/auth/index.tsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 | import { UserRegister, UserLogin } from './api_action';
3 |
4 | // start user register
5 |
6 | interface UserRegisterState {
7 | newUser: {},
8 | error: Object | null | undefined
9 | }
10 |
11 | const initialStateUserRegister = {
12 | newUser: {},
13 | error: null,
14 | } as UserRegisterState;
15 |
16 | const UserRegisterSlice = createSlice({
17 | name: 'userRegister',
18 | initialState: initialStateUserRegister,
19 | reducers: {},
20 | extraReducers: {
21 | [UserRegister.fulfilled.type]: (state, action) => {
22 | state.newUser = action.payload;
23 | },
24 | [UserRegister.rejected.type]: (state, action) => {
25 | state.error = action.error.message;
26 | },
27 | },
28 | });
29 |
30 | export { UserRegister };
31 | export const userRegisterReducer = UserRegisterSlice.reducer;
32 |
33 | // start user login
34 |
35 | interface UserLoginState {
36 | userData: {},
37 | error: Object | null | undefined
38 | }
39 |
40 | const initialStateUserLogin = {
41 | userData: {},
42 | error: null,
43 | } as UserLoginState;
44 |
45 | const UserLoginSlice = createSlice({
46 | name: 'userLogin',
47 | initialState: initialStateUserLogin,
48 | reducers: {},
49 | extraReducers: {
50 | [UserLogin.fulfilled.type]: (state, action) => {
51 | state.userData = action.payload;
52 | },
53 | [UserLogin.rejected.type]: (state, action) => {
54 | state.error = action.error.message;
55 | },
56 | },
57 | });
58 |
59 | export { UserLogin };
60 | export const userLoginReducer = UserLoginSlice.reducer;
61 |
--------------------------------------------------------------------------------
/src/router/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, lazy } from 'react';
2 | import {
3 | BrowserRouter,
4 | Routes,
5 | Route,
6 | Link,
7 | Navigate,
8 | } from 'react-router-dom';
9 | import { Loading } from '../components';
10 | import PrivateRoute from '../components/PrivateRoute';
11 |
12 | const Home = lazy(() =>
13 | import('../containers')
14 | .then(({ Home }) => ({ default: Home })),
15 | );
16 |
17 | const Login = lazy(() =>
18 | import('../containers')
19 | .then(({ Login }) => ({ default: Login })),
20 | );
21 |
22 | const Users = lazy(() =>
23 | import('../containers')
24 | .then(({ Users }) => ({ default: Users })),
25 | );
26 |
27 | const Tasks = lazy(() =>
28 | import('../containers')
29 | .then(({ Tasks }) => ({ default: Tasks })),
30 | );
31 |
32 | export const Router = () => {
33 | return (
34 | }>
35 |
36 |
40 |
41 |
42 | }
43 | />
44 |
48 |
49 |
50 | }
51 | />
52 |
56 |
57 |
58 | }
59 | />
60 | } />
61 | } />
62 |
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/src/store/users/index.tsx:
--------------------------------------------------------------------------------
1 | import {createSlice} from '@reduxjs/toolkit';
2 | import {UserData, UserCreate} from './api_action';
3 | import {IUsers} from "../../models";
4 |
5 | interface UserState {
6 | userData: IUsers[],
7 | error: Object | null | undefined
8 | }
9 |
10 | const initialStateUserData = {
11 | userData: [],
12 | error: null
13 | } as UserState
14 |
15 | const cache: any = localStorage.getItem('AllUsers')
16 |
17 | const UserDataSlice = createSlice({
18 | name: 'userData',
19 | initialState: initialStateUserData,
20 | reducers: {},
21 | extraReducers: {
22 | [UserData.fulfilled.type]: (state, action) => {
23 | state.userData = navigator.onLine ? action.payload : JSON.parse(cache)
24 | },
25 | [UserData.rejected.type]: (state, action) => {
26 | state.error = action.error.message
27 | state.userData = JSON.parse(cache)
28 | }
29 | }
30 | })
31 |
32 | export {UserData}
33 | export const userDataReducer = UserDataSlice.reducer
34 |
35 | // start user create
36 |
37 | interface UserCreateState {
38 | newUser: {},
39 | error: Object | null | undefined
40 | }
41 |
42 | const initialStateUserCreate = {
43 | newUser: {},
44 | error: null
45 | } as UserCreateState
46 |
47 | const UserCreateSlice = createSlice({
48 | name: 'userCreate',
49 | initialState: initialStateUserCreate,
50 | reducers: {},
51 | extraReducers: {
52 | [UserCreate.fulfilled.type]: (state, action) => {
53 | state.newUser = action.payload
54 | },
55 | [UserCreate.rejected.type]: (state, action) => {
56 | state.error = action.error.message
57 | }
58 | }
59 | })
60 |
61 | export {UserCreate}
62 | export const userCreateReducer = UserCreateSlice.reducer
63 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Menu, Layout, theme, Button } from 'antd';
3 | import type { MenuProps } from 'antd';
4 | import { Link, useNavigate } from 'react-router-dom';
5 | import styled from 'styled-components';
6 | import { useSelector } from 'react-redux';
7 | import { RootState } from '../store';
8 |
9 | const { Header } = Layout;
10 |
11 | export const HeaderComponent: React.FC = () => {
12 | const navigate = useNavigate();
13 | const localeSotore = localStorage.getItem('userData');
14 |
15 | const handleClick = () => {
16 | localStorage.removeItem('userData');
17 | navigate('/');
18 | };
19 |
20 | return (
21 |
22 |
23 | {!localeSotore ? (
24 |
31 | ) : (
32 |
47 | )}
48 |
49 | );
50 | };
51 |
52 | const StyledHeader = styled(Header)`
53 | display: flex;
54 | justify-content: flex-end;
55 | background-color: #fff !important;
56 |
57 | ul {
58 | width: 250px;
59 | }
60 | `;
61 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React Redux App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/containers/Auth/Login/SignIn.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { useNavigate } from 'react-router-dom';
4 | import { Button, Checkbox, Form, Input } from 'antd';
5 |
6 | import { RootState } from '../../../store';
7 | import { UserLogin } from '../../../store/auth';
8 | import { Loading } from '../../../components';
9 |
10 | /* eslint-disable no-template-curly-in-string */
11 | const validateMessages = {
12 | required: '${label} is required!',
13 | types: {
14 | email: '${label} is not a valid email!',
15 | },
16 | };
17 | /* eslint-enable no-template-curly-in-string */
18 |
19 | export const SignIn: React.FC = () => {
20 |
21 | const navigate = useNavigate();
22 | const dispatch = useDispatch();
23 |
24 | const userData = useSelector((state: RootState) => state.login.userData);
25 | //loading
26 | const allLoading = useSelector((state: RootState) => state.loadings);
27 |
28 | const onFinish = (values: any) => {
29 | // @ts-ignore
30 | dispatch(UserLogin(values));
31 | };
32 |
33 | useEffect(() => {
34 | Object.keys(userData).length !== 0 && navigate('/')
35 | }, [userData])
36 |
37 | return (
38 |
39 |
42 |
49 |
50 |
51 |
57 |
58 |
59 |
60 |
61 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) TS template.
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smart-solutions-task",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.9.1",
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^13.4.0",
9 | "@testing-library/user-event": "^14.4.3",
10 | "@types/jest": "^27.5.2",
11 | "@types/node": "^17.0.45",
12 | "@types/react": "^18.0.26",
13 | "@types/react-dom": "^18.0.9",
14 | "@types/react-redux": "^7.1.24",
15 | "@types/react-router-dom": "^5.3.3",
16 | "@types/styled-components": "^5.1.26",
17 | "@types/uuid": "^9.0.0",
18 | "antd": "^5.0.6",
19 | "axios": "^1.2.1",
20 | "concurrently": "^7.6.0",
21 | "cors": "^2.8.5",
22 | "express": "^4.18.2",
23 | "gh-pages": "^4.0.0",
24 | "http": "^0.0.1-security",
25 | "nodeman": "^1.1.2",
26 | "react": "^18.2.0",
27 | "react-dom": "^18.2.0",
28 | "react-redux": "^8.0.5",
29 | "react-router-dom": "^6.4.5",
30 | "react-scripts": "5.0.1",
31 | "react-toastify": "^9.1.1",
32 | "styled-components": "^5.3.6",
33 | "uuid": "^9.0.0",
34 | "web-vitals": "^2.1.4"
35 | },
36 | "scripts": {
37 | "start": "react-scripts start",
38 | "predeploy": "npm run build",
39 | "deploy": "gh-pages -d build",
40 | "start-server": "node ./server/server",
41 | "build": "react-scripts build",
42 | "dev": "concurrently \"npm run start-server\" \"npm run start\"",
43 | "test": "react-scripts test",
44 | "eject": "react-scripts eject"
45 | },
46 | "eslintConfig": {
47 | "extends": [
48 | "react-app",
49 | "react-app/jest"
50 | ]
51 | },
52 | "browserslist": {
53 | "production": [
54 | ">0.2%",
55 | "not dead",
56 | "not op_mini all"
57 | ],
58 | "development": [
59 | "last 1 chrome version",
60 | "last 1 firefox version",
61 | "last 1 safari version"
62 | ]
63 | },
64 | "devDependencies": {
65 | "@typescript-eslint/eslint-plugin": "^5.46.1",
66 | "@typescript-eslint/parser": "^5.46.1",
67 | "eslint": "^8.29.0",
68 | "eslint-config-prettier": "^8.5.0",
69 | "eslint-config-xo": "^0.43.1",
70 | "eslint-config-xo-typescript": "^0.55.1",
71 | "eslint-plugin-prettier": "^4.2.1",
72 | "eslint-plugin-react": "^7.31.11",
73 | "prettier": "^2.8.1",
74 | "typescript": "^4.9.4"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/containers/Auth/Login/SignUp.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { useNavigate } from 'react-router-dom';
4 | import { Alert, Button, Form, Input, InputNumber } from 'antd';
5 | import styled from 'styled-components';
6 | import { v4 } from 'uuid';
7 |
8 | import { RootState } from '../../../store';
9 | import { UserRegister } from '../../../store/auth';
10 | import { Loading } from '../../../components';
11 |
12 | const layout = {
13 | labelCol: { span: 8 },
14 | wrapperCol: { span: 16 },
15 | };
16 |
17 | /* eslint-disable no-template-curly-in-string */
18 | const validateMessages = {
19 | required: '${label} is required!',
20 | types: {
21 | email: '${label} is not a valid email!',
22 | },
23 | };
24 | /* eslint-enable no-template-curly-in-string */
25 |
26 | export const SignUp: React.FC = () => {
27 |
28 | const navigate = useNavigate();
29 | const dispatch = useDispatch();
30 |
31 | //loading
32 | const allLoading = useSelector((state: RootState) => state.loadings);
33 |
34 | const onFinish = (values: any) => {
35 | const setValue = {
36 | ...values,
37 | id: v4(),
38 | isAdmin: false,
39 | users: [],
40 | };
41 | // @ts-ignore
42 | dispatch(UserRegister(setValue));
43 | };
44 |
45 | return (
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 |
86 | const StyledForm = styled(Form)`
87 | min-width: 400px;
88 | `;
89 |
90 |
--------------------------------------------------------------------------------
/routes/item.js:
--------------------------------------------------------------------------------
1 | // import required essentials
2 | const express = require('express');
3 | const { statuses } = require('./static-mocks');
4 | // create new router
5 | const router = express.Router();
6 |
7 | // create a JSON data array
8 | const tasks = [
9 | {
10 | id: '45@#9089&3_5#$',
11 | // users: [
12 | // users[1],
13 | // users[0]
14 | // ],
15 | title: 'Login UI',
16 | description: 'Create modern Internet Banking UI for Login page',
17 | deadline: '25-01-2023',
18 | status: statuses[0],
19 | },
20 | {
21 | id: 'sd34_234+#089',
22 | // users: [
23 | // users[1]
24 | // ],
25 | title: 'Sign-up UI',
26 | description: 'Create modern Internet Banking UI for Sign-up page',
27 | deadline: '12-02-2023',
28 | status: statuses[1],
29 | },
30 | ];
31 |
32 | const users = [
33 | {
34 | id: '2@13dfsgsdf_234',
35 | name: 'Ebulfez',
36 | surname: 'Elbeyov',
37 | email: 'ebulfez@mc.az',
38 | password: 'ebulfez@',
39 | tasks: [
40 | tasks[0],
41 | tasks[1],
42 | ],
43 | },
44 | {
45 | id: '2@13dfsgsdf_234',
46 | name: 'Elgun',
47 | surname: 'Elyrov',
48 | email: 'elgun@mc.az',
49 | password: 'elgun@',
50 | tasks: [
51 | tasks[1],
52 | ],
53 | },
54 | ];
55 |
56 | const data = [
57 | {
58 | id: '1',
59 | orginzationName: 'MC LLC',
60 | phoneNumber: '0704400500',
61 | address: 'Yasamal Xray.',
62 | userName: 'Amil Abdullayev',
63 | email: 'amila@mc.az',
64 | password: 'amil95@',
65 | isAdmin: true,
66 | users: users,
67 | },
68 | ];
69 |
70 | async function getData(req, res) {
71 | res.status(200).json(data);
72 | }
73 |
74 | async function getFindedData(req, res) {
75 | // find an object from `data` array match by `id`
76 | let found = data.find(function(item) {
77 | return item.id === parseInt(req.params.id);
78 | });
79 | // if object found return an object else return 404 not-found
80 | if (found) {
81 | res.status(200).json(found);
82 | } else {
83 | res.sendStatus(404);
84 | }
85 | }
86 |
87 | async function login(req, res) {
88 | let foundInOrginzation = data.find(function(item) {
89 | return item.email === req.body.email;
90 | });
91 | let foundInUsers = Object.assign({}, ...data.map(item => item.users.find(user => {
92 | return user.email === req.body.email;
93 | })));
94 |
95 | if (!foundInOrginzation && !foundInUsers) {
96 | return res.status(404).send({ message: 'User Not found.' });
97 | }
98 |
99 | const passwordIsValid = (password) => {
100 | return password === req.body.password;
101 | };
102 |
103 | const passIsValid = (passwordIsValid(foundInOrginzation?.password) || passwordIsValid(foundInUsers?.password));
104 |
105 | if (!passIsValid) {
106 | return res.status(401).send({
107 | message: 'Invalid Password!',
108 | });
109 | }
110 |
111 | if (foundInOrginzation) {
112 | res.status(200).json(foundInOrginzation);
113 | } else if (foundInUsers) {
114 | res.status(200).json(foundInUsers);
115 | } else {
116 | res.sendStatus(404);
117 | }
118 | }
119 |
120 | async function register(req, res) {
121 | data.push(req.body);
122 |
123 | res.status(201).json(req.body);
124 | }
125 |
126 | async function getUsers(req, res) {
127 | res.status(200).json(users);
128 | }
129 |
130 | async function createUser(req, res) {
131 | users.push(req.body);
132 |
133 | res.status(201).json(req.body);
134 | }
135 |
136 | module.exports = {
137 | getData,
138 | getFindedData,
139 | login,
140 | register,
141 | getUsers,
142 | createUser
143 | };
144 |
--------------------------------------------------------------------------------
/src/containers/Users/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState, useRef, memo, useCallback} from 'react';
2 | import {useDispatch, useSelector} from "react-redux";
3 | import {toast, ToastContainer} from "react-toastify";
4 | import {useNavigate, Link} from "react-router-dom";
5 | import plusIcon from "../../assets/icons/plus-circle.svg";
6 | import {RootState} from "../../store";
7 | import {UserData} from "../../store/users";
8 | import {Loading} from "../../components";
9 | import {Button} from "../../components/styled";
10 | import {Space, Table, Tag} from 'antd';
11 | import styled from "styled-components";
12 |
13 | export const Users = memo(() => {
14 | const dispatch = useDispatch()
15 | const didMountRef = useRef()
16 |
17 | const [reload, setReload] = useState(false)
18 | const [users, setUsers] = useState([])
19 |
20 | let userData = useSelector((state: RootState) => state.users.userData)
21 | const errorUser = useSelector((state: RootState) => state.users.error)
22 | //loading
23 | const allLoading = useSelector((state: RootState) => state.loadings);
24 |
25 | interface DataType {
26 | id: string
27 | name: string
28 | surname: string
29 | email: string
30 | password: string
31 | tasks: string[]
32 | }
33 |
34 | useEffect(() => {
35 | if (errorUser) {
36 | // @ts-ignore
37 | toast.error(errorUser, {
38 | position: "top-right",
39 | autoClose: 5000,
40 | });
41 | }
42 | }, [errorUser])
43 |
44 | useEffect(() => {
45 | // @ts-ignore
46 | dispatch(UserData())
47 |
48 | if (!navigator.onLine) {
49 | toast.error('No Internet Connection', {
50 | position: "top-right",
51 | autoClose: 5000,
52 | });
53 | }
54 | }, [reload])
55 |
56 | // id: string
57 | // name: string
58 | // surname: string
59 | // email: string
60 | // password: string
61 | // tasks: string[]
62 |
63 | const columns = [
64 | {
65 | title: 'id',
66 | dataIndex: 'id',
67 | key: 'id'
68 | },
69 | {
70 | title: 'Name',
71 | dataIndex: 'name',
72 | key: 'name'
73 | },
74 | {
75 | title: 'Surname',
76 | dataIndex: 'surname',
77 | key: 'surname'
78 | },
79 | {
80 | title: 'Email',
81 | dataIndex: 'email',
82 | key: 'email'
83 | },
84 | {
85 | title: 'Password',
86 | dataIndex: 'password',
87 | key: 'password',
88 | },
89 | // {
90 | // title: 'tasks',
91 | // dataIndex: 'tasks',
92 | // key: 'tasks',
93 | // render: (tasks: string) => {
94 | // let color = status === 'completed' ? 'green' : status === 'uncompleted' ? 'geekblue' : 'volcano';
95 | // return (
96 | //
97 | //
98 | // {status.toUpperCase()}
99 | //
100 | //
101 | // )
102 | // }
103 | // },
104 | ]
105 |
106 | useEffect(() => {
107 | //ref vasitəsi ilə ilk renderin qarşısını alırıq
108 | if (didMountRef.current) {
109 | const usersData = userData?.map((user, idx) => {
110 | const {
111 | id,
112 | name,
113 | surname,
114 | email,
115 | password,
116 | // tasks
117 | } = user;
118 | return ({
119 | key: id,
120 | name: name,
121 | surname: surname,
122 | email: email,
123 | password: password,
124 | // tasks: tasks,
125 | })
126 | })
127 | Promise.all(usersData).then(function (results) {
128 | // results.sort((a, b) => a.status.localeCompare(b.status));
129 | // @ts-ignore
130 | return setUsers(results);
131 | })
132 | } else {
133 | // @ts-ignore
134 | didMountRef.current = true;
135 | }
136 | }, [userData])
137 |
138 | return (
139 |
140 |
143 |
Create new user
144 |
145 | {!localStorage.getItem('AllData') && }
146 |
147 |
148 | )
149 | })
150 |
151 | export const StyledButton = styled(Link)`
152 | buser: none;
153 | padding: 10px 25px;
154 | text-align: center;
155 | cursor: pointer;
156 | font-size: 14px;
157 | buser-radius: 6px;
158 | color: #fff;
159 | background-color: #1677ff;
160 | box-shadow: 0 2px 0 rgb(5 145 255 / 10%);
161 | transition: all linear .35s;
162 |
163 | &:hover {
164 | opacity: .7;
165 | }
166 | `
167 |
168 | export const StyledTableHeader = styled.div`
169 | width: 100%;
170 | height: 75px;
171 | display: flex;
172 | justify-content: flex-end;
173 | align-items: center;
174 |
175 | span {
176 | margin-bottom: 2px;
177 | }
178 |
179 | a {
180 | buser: none;
181 | padding: 15px 10px;
182 | text-align: center;
183 | cursor: pointer;
184 | font-size: 14px;
185 | buser-radius: 6px;
186 | color: #fff;
187 | background-color: green;
188 | box-shadow: 0 2px 0 rgb(5 145 255 / 10%);
189 | transition: all linear .35s;
190 | text-decoration: none;
191 | display: flex;
192 | align-items: center;
193 | column-gap: 7px;
194 |
195 | &:hover {
196 | opacity: .7;
197 | }
198 | }
199 | `
200 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
212 |
213 |
214 |
215 |
216 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
--------------------------------------------------------------------------------