├── 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 | 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 | 25 | 26 | 27 | Login or Register 28 | 29 | 30 | 31 | ) : ( 32 | 33 | 34 | 35 | Users 36 | 37 | 38 | 39 | 40 | Tasks 41 | 42 | 43 | 44 | 45 | 46 | 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 |
48 | 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 | 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 | plus icon 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 | 561 | --------------------------------------------------------------------------------