├── .env ├── .env.example ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── babel.config.js ├── dist ├── 0.9c0f81488d5788f4d661.js ├── 0d0de35b6447df412926c60e216ccfa8.png ├── 1.9c0f81488d5788f4d661.js ├── 1.9c0f81488d5788f4d661.js.LICENSE.txt ├── 12.9c0f81488d5788f4d661.js ├── 13.9c0f81488d5788f4d661.js ├── 14.9c0f81488d5788f4d661.js ├── 15.9c0f81488d5788f4d661.js ├── 16.9c0f81488d5788f4d661.js ├── 17.9c0f81488d5788f4d661.js ├── 18.9c0f81488d5788f4d661.js ├── 19.9c0f81488d5788f4d661.js ├── 2.9c0f81488d5788f4d661.js ├── 20.9c0f81488d5788f4d661.js ├── 2b25ac03edabf67270554b3d790c43fe.woff ├── 884ea0c2506448bcda32c9dfe0d09e7a.png ├── de8075ae6f7bce20fcab444e8d1aa960.woff ├── favicon.png ├── index.html ├── main~1f20a385.9c0f81488d5788f4d661.js ├── main~21833f8f.9c0f81488d5788f4d661.js ├── main~253ae210.9c0f81488d5788f4d661.js ├── main~31ecd969.9c0f81488d5788f4d661.js ├── main~31ecd969.9c0f81488d5788f4d661.js.LICENSE.txt ├── main~7d359b94.9c0f81488d5788f4d661.js ├── main~7d359b94.9c0f81488d5788f4d661.js.LICENSE.txt ├── main~93acefaf.9c0f81488d5788f4d661.js ├── main~987e6011.9c0f81488d5788f4d661.js ├── main~d939e436.9c0f81488d5788f4d661.js ├── main~f9ca8911.9c0f81488d5788f4d661.js └── main~f9ca8911.9c0f81488d5788f4d661.js.LICENSE.txt ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── assets │ ├── fonts │ │ ├── Poppins │ │ │ └── Poppins-Regular.woff │ │ └── Roboto │ │ │ └── Roboto-Regular.woff │ └── images │ │ ├── favicon.png │ │ ├── github │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ │ ├── james.png │ │ ├── team1.png │ │ └── team2.png ├── components │ ├── Activity │ │ └── index.tsx │ ├── App.tsx │ ├── Common │ │ ├── Avatar │ │ │ └── index.tsx │ │ ├── Buttons │ │ │ └── Big │ │ │ │ └── index.tsx │ │ ├── Checkbox │ │ │ └── index.tsx │ │ ├── ErrorBoundary │ │ │ └── index.jsx │ │ ├── GlobalStyle │ │ │ └── index.jsx │ │ ├── Header │ │ │ ├── Actions │ │ │ │ ├── Button │ │ │ │ │ └── index.tsx │ │ │ │ ├── Search │ │ │ │ │ └── index.tsx │ │ │ │ ├── Shape │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Icons │ │ │ ├── Common │ │ │ │ ├── Attach.tsx │ │ │ │ ├── Close.tsx │ │ │ │ ├── Oval.tsx │ │ │ │ └── Shape.tsx │ │ │ ├── Menu │ │ │ │ ├── Activity.tsx │ │ │ │ ├── Dashboard.tsx │ │ │ │ ├── Messages.tsx │ │ │ │ ├── Schedule.tsx │ │ │ │ ├── Settings.tsx │ │ │ │ └── Tasks.tsx │ │ │ ├── SocialMedia │ │ │ │ ├── Facebook.tsx │ │ │ │ └── Google.tsx │ │ │ └── Switcher │ │ │ │ ├── Default.tsx │ │ │ │ ├── Gantt.tsx │ │ │ │ └── Kanban.tsx │ │ ├── Loader │ │ │ └── index.tsx │ │ ├── Logo │ │ │ ├── Image │ │ │ │ └── index.tsx │ │ │ ├── Title │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── PrivateRoute │ │ │ └── index.tsx │ │ ├── Sidebar │ │ │ ├── Header │ │ │ │ ├── Button │ │ │ │ │ └── index.tsx │ │ │ │ ├── Items │ │ │ │ │ └── index.tsx │ │ │ │ ├── Teams │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── Menu │ │ │ │ ├── Item │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Task │ │ │ ├── Info │ │ │ │ └── index.tsx │ │ │ ├── Score │ │ │ │ └── index.tsx │ │ │ ├── Titles │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── TaskModal │ │ │ ├── Description │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── TaskWrapper │ │ │ ├── Button │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ └── TeamCard │ │ │ └── index.tsx │ ├── Login │ │ ├── Form │ │ │ ├── Submit │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Menu │ │ │ └── index.tsx │ │ ├── Social │ │ │ └── index.tsx │ │ └── index.tsx │ ├── Main │ │ ├── Content │ │ │ ├── Tasks │ │ │ │ └── index.tsx │ │ │ ├── Teams │ │ │ │ └── index.tsx │ │ │ ├── Title │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ └── index.tsx │ ├── Messages │ │ └── index.tsx │ ├── Schedule │ │ └── index.tsx │ ├── Settings │ │ └── index.tsx │ └── Tasks │ │ ├── Content │ │ ├── Title │ │ │ ├── Selector │ │ │ │ └── index.tsx │ │ │ ├── Switcher │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ └── index.tsx │ │ └── index.tsx ├── index.html ├── index.tsx ├── store │ ├── auth │ │ ├── actions.ts │ │ ├── reducers.ts │ │ ├── selectors.ts │ │ └── types.ts │ ├── index.ts │ ├── show │ │ ├── actions.ts │ │ ├── reducers.ts │ │ ├── selectors.ts │ │ └── types.ts │ ├── tasks │ │ ├── actions.ts │ │ ├── reducers.ts │ │ ├── selectors.ts │ │ └── types.ts │ └── teams │ │ ├── actions.ts │ │ ├── reducers.ts │ │ ├── selectors.ts │ │ └── types.ts └── utils │ ├── axios.ts │ └── idGenerator.ts ├── tsconfig.json └── webpack.config.js /.env: -------------------------------------------------------------------------------- 1 | API_URL="https://square-dashboard-6ffae.firebaseio.com" 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_URL="" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .env 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2019 Egor Safronov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Square React Dashboard Template 2 | 3 | Built with [Webpack](https://webpack.js.org/), [Babel](https://babeljs.io/), [TypeScript](https://www.typescriptlang.org/), [React](https://reactjs.org/), [React Router](https://reacttraining.com/react-router/web/guides/quick-start), [Redux](https://redux.js.org/), [styled-components](https://www.styled-components.com/) and latest industry best practices. 4 | 5 | > No create-react-app, classes react components, Bootstrap and jQuery. 6 | 7 | Plus you will see how to use correctly: 8 | 9 | - [axios](https://github.com/axios/axios) 10 | - [Reselect](https://github.com/reduxjs/reselect) 11 | - [Redux Thunk](https://github.com/reduxjs/redux-thunk) 12 | - React memo 13 | - React Hooks 14 | - React Portals 15 | - React lazy & Suspense 16 | - React Error Boundaries 17 | 18 | ## Demo 19 | 20 | [Link](https://heysafronov.github.io/square-react-dashboard/dist/#/login). Use login and password "admin" to log in. 21 | 22 | Try to use drag and drop and other functionality. If this repository is interest and will get more than 10 stars, then the plan is will expanding current functionality and writing Style Guide for medium+ projects with React + TypeScript technical stack. 23 | 24 | 25 | 26 | ## 27 | 28 | 29 | 30 | ## 31 | 32 | 33 | 34 | ## 35 | 36 | 37 | 38 | ## 39 | 40 | 41 | 42 | ## Back-end 43 | 44 | Using [Firebase](https://firebase.google.com/) as simple backend. 45 | 46 | ## Browser support 47 | 48 | Compatible with every evergreen browser. 49 | 50 | ## Web design 51 | 52 | Iconspace. [Stylish Dashboard UI Kit for Various Need](https://iconspace.co/product/square-dashboard-ui-kit/). Made with a unique style using rounded corner shape & modern color scheme. Make it looks dynamic and matches with the current and future design styles. 53 | 54 | ## License 55 | 56 | Licensed under [MIT](https://github.com/heysafronov/square-react-dashboard/blob/master/LICENSE). 57 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true) 3 | 4 | const presets = [ 5 | '@babel/preset-env', 6 | '@babel/preset-typescript', 7 | '@babel/preset-react' 8 | ] 9 | const plugins = [ 10 | [ 11 | 'babel-plugin-styled-components', 12 | { 13 | displayName: true, 14 | fileName: true 15 | } 16 | ], 17 | '@babel/proposal-class-properties', 18 | '@babel/proposal-object-rest-spread' 19 | ] 20 | 21 | return { 22 | presets, 23 | plugins 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /dist/0d0de35b6447df412926c60e216ccfa8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heysafronov/square-react-dashboard/f12759a0980299583134e6e7b9ba5d34521d47d5/dist/0d0de35b6447df412926c60e216ccfa8.png -------------------------------------------------------------------------------- /dist/1.9c0f81488d5788f4d661.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Determine if an object is a Buffer 3 | * 4 | * @author Feross Aboukhadijeh 5 | * @license MIT 6 | */ 7 | -------------------------------------------------------------------------------- /dist/12.9c0f81488d5788f4d661.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[12],{434:function(e,t,n){"use strict";var a=n(0),r=n.n(a),o=n(48),i=function(e){var t=e.size,n=e.color;return r.a.createElement("svg",{width:"".concat(t),height:"".concat(t),viewBox:"0 0 50 50",fill:"none",xmlns:"http://www.w3.org/2000/svg"},r.a.createElement("rect",{x:"3",y:"6.5",width:"36",height:"36",rx:"12",stroke:"".concat(n),strokeWidth:"6"}),r.a.createElement("rect",{x:"19.5",y:"8",width:"18",height:"18",rx:"9",stroke:"".concat(n),strokeWidth:"6"}))},l=o.default.span.withConfig({displayName:"Title__Wrapper",componentId:"sc-18m4a6c-0"})(["font-size:","px;color:",";font-weight:900;margin-left:5px;"],(function(e){return e.size}),(function(e){return e.color})),c=function(e){var t=e.text;return r.a.createElement(l,e,t)},u=o.default.section.withConfig({displayName:"Logo__Section",componentId:"sc-1ifyy6a-0"})(["display:flex;align-items:center;justify-content:space-between;"]);t.a=function(e){var t=e.image,n=e.title;return r.a.createElement(u,null,r.a.createElement(i,t),r.a.createElement(c,n))}},474:function(e,t,n){"use strict";n.r(t);var a=n(0),r=n.n(a),o=n(48),i=n(434),l=function(){return r.a.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg"},r.a.createElement("mask",{id:"mask0",maskUnits:"userSpaceOnUse",x:"0",y:"0",width:"16",height:"16"},r.a.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M15.814 6.54545H8.18605V9.63636H12.5767C12.1674 11.6 10.4558 12.7273 8.18605 12.7273C5.50698 12.7273 3.34884 10.6182 3.34884 8C3.34884 5.38182 5.50698 3.27273 8.18605 3.27273C9.33953 3.27273 10.3814 3.67273 11.2 4.32727L13.5814 2C12.1302 0.763636 10.2698 0 8.18605 0C3.64651 0 0 3.56364 0 8C0 12.4364 3.64651 16 8.18605 16C12.2791 16 16 13.0909 16 8C16 7.52727 15.9256 7.01818 15.814 6.54545Z",fill:"white"})),r.a.createElement("g",{mask:"url(#mask0)"},r.a.createElement("path",{d:"M-0.744141 12.7275V3.27295L5.58144 8.00022L-0.744141 12.7275Z",fill:"white"})),r.a.createElement("mask",{id:"mask1",maskUnits:"userSpaceOnUse",x:"0",y:"0",width:"16",height:"16"},r.a.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M15.814 6.54545H8.18605V9.63636H12.5767C12.1674 11.6 10.4558 12.7273 8.18605 12.7273C5.50698 12.7273 3.34884 10.6182 3.34884 8C3.34884 5.38182 5.50698 3.27273 8.18605 3.27273C9.33953 3.27273 10.3814 3.67273 11.2 4.32727L13.5814 2C12.1302 0.763636 10.2698 0 8.18605 0C3.64651 0 0 3.56364 0 8C0 12.4364 3.64651 16 8.18605 16C12.2791 16 16 13.0909 16 8C16 7.52727 15.9256 7.01818 15.814 6.54545Z",fill:"white"})),r.a.createElement("g",{mask:"url(#mask1)"},r.a.createElement("path",{d:"M-0.744141 3.27295L5.58144 8.00022L8.18609 5.78204L17.1163 4.36386V-0.727051H-0.744141V3.27295Z",fill:"white"})),r.a.createElement("mask",{id:"mask2",maskUnits:"userSpaceOnUse",x:"0",y:"0",width:"16",height:"16"},r.a.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M15.814 6.54545H8.18605V9.63636H12.5767C12.1674 11.6 10.4558 12.7273 8.18605 12.7273C5.50698 12.7273 3.34884 10.6182 3.34884 8C3.34884 5.38182 5.50698 3.27273 8.18605 3.27273C9.33953 3.27273 10.3814 3.67273 11.2 4.32727L13.5814 2C12.1302 0.763636 10.2698 0 8.18605 0C3.64651 0 0 3.56364 0 8C0 12.4364 3.64651 16 8.18605 16C12.2791 16 16 13.0909 16 8C16 7.52727 15.9256 7.01818 15.814 6.54545Z",fill:"white"})),r.a.createElement("g",{mask:"url(#mask2)"},r.a.createElement("path",{d:"M-0.744141 12.7275L10.4186 4.36386L13.3582 4.72749L17.1163 -0.727051V16.7275H-0.744141V12.7275Z",fill:"white"})),r.a.createElement("mask",{id:"mask3",maskUnits:"userSpaceOnUse",x:"0",y:"0",width:"16",height:"16"},r.a.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M15.814 6.54545H8.18605V9.63636H12.5767C12.1674 11.6 10.4558 12.7273 8.18605 12.7273C5.50698 12.7273 3.34884 10.6182 3.34884 8C3.34884 5.38182 5.50698 3.27273 8.18605 3.27273C9.33953 3.27273 10.3814 3.67273 11.2 4.32727L13.5814 2C12.1302 0.763636 10.2698 0 8.18605 0C3.64651 0 0 3.56364 0 8C0 12.4364 3.64651 16 8.18605 16C12.2791 16 16 13.0909 16 8C16 7.52727 15.9256 7.01818 15.814 6.54545Z",fill:"white"})),r.a.createElement("g",{mask:"url(#mask3)"},r.a.createElement("path",{d:"M17.116 16.7275L5.58115 8.00022L4.09277 6.90931L17.116 3.27295V16.7275Z",fill:"white"})))},c=function(){return r.a.createElement("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg"},r.a.createElement("path",{d:"M15.0782 15.957C15.5634 15.957 15.9569 15.5635 15.9569 15.0782V0.914836C15.9569 0.429461 15.5635 0.0361328 15.0782 0.0361328H0.914836C0.429461 0.0361328 0.0361328 0.429461 0.0361328 0.914836V15.0782C0.0361328 15.5635 0.429399 15.957 0.914836 15.957H15.0782Z",fill:"white"}),r.a.createElement("path",{d:"M11.0219 15.9572V9.79178H13.0913L13.4012 7.389H11.0219V5.85497C11.0219 5.15931 11.215 4.68524 12.2126 4.68524L13.4849 4.68469V2.53562C13.2649 2.50634 12.5096 2.44092 11.6309 2.44092C9.79648 2.44092 8.54057 3.56067 8.54057 5.61701V7.389H6.46582V9.79178H8.54057V15.9572H11.0219Z",fill:"#50B5FF"}))},u=o.default.div.withConfig({displayName:"Social__Wrapper",componentId:"sc-1o22sf9-0"})(["display:flex;flex-direction:column;align-items:center;"]),s=o.default.span.withConfig({displayName:"Social__Text",componentId:"sc-1o22sf9-1"})(["margin:15px 0;color:#92929d;font-size:12px;text-transform:uppercase;"]),d=o.default.button.withConfig({displayName:"Social__ButtonGoogle",componentId:"sc-1o22sf9-2"})(["display:flex;align-items:center;justify-content:center;width:320px;height:38px;border:none;background:#fc5a5a;border-radius:10px;cursor:not-allowed;:focus{outline:none;}span{margin-left:10px;color:white;font-size:12px;}"]),p=Object(o.default)(d).withConfig({displayName:"Social__ButtonFacebook",componentId:"sc-1o22sf9-3"})(["background:#50b5ff;margin-top:10px;"]),f=function(){return r.a.createElement(u,null,r.a.createElement(s,null,"or"),r.a.createElement(d,null,r.a.createElement(l,null),r.a.createElement("span",null,"Continue with Google")),r.a.createElement(p,null,r.a.createElement(c,null),r.a.createElement("span",null,"Continue with Facebook")))},m=n(75),h=n(47),g=n(172);function w(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function y(e){for(var t=1;t0&&r.a.createElement(g,e,r.a.createElement(u.a,null),r.a.createElement("span",null,t.score.days),r.a.createElement("span",null,"days left")))},x=n(428),w=i.default.div.withConfig({displayName:"Score__Wrapper",componentId:"sc-1fdjcxw-0"})(["display:flex;flex-direction:",";align-items:",";"],(function(e){return e.option?"column":"row-reverse"}),(function(e){return!e.option&&"center"})),b=i.default.div.withConfig({displayName:"Score__ScoreLine",componentId:"sc-1fdjcxw-1"})(["background-color:#e2e2ea;width:100%;height:3px;border-radius:2.5px;min-width:",";div{height:3px;background-color:#3dd598;width:",";}"],(function(e){return!e.option&&"150px"}),(function(e){return"".concat(e.data.line,"%")})),v=i.default.div.withConfig({displayName:"Score__ScoreLineTitle",componentId:"sc-1fdjcxw-2"})(["font-size:14px;letter-spacing:0.1px;color:#696974;display:flex;justify-content:flex-end;width:100%;margin-left:",";"],(function(e){return!e.option&&"10px"})),C=Object(o.b)((function(e){return{option:Object(x.a)(e)}}))((function(e){var t=e.data;return r.a.createElement(w,e,r.a.createElement(v,e,t.line,"%"),r.a.createElement(b,e,r.a.createElement("div",null)))})),y=i.default.div.withConfig({displayName:"Titles__TextStyles",componentId:"sc-8t8vso-0"})(["font-size:14px;letter-spacing:0.1px;color:#92929d;"]),E=i.default.div.withConfig({displayName:"Titles__Wrapper",componentId:"sc-8t8vso-1"})(["display:flex;flex-direction:column;"]),_=Object(i.default)(y).withConfig({displayName:"Titles__Title",componentId:"sc-8t8vso-2"})(["color:#171725;margin-bottom:7px;text-decoration:",";"],(function(e){return 0===e.data.score.days&&"line-through"})),j=Object(i.default)(y).withConfig({displayName:"Titles__Team",componentId:"sc-8t8vso-3"})(["color:#696974;"]),k=function(e){var t=e.data;return r.a.createElement(E,null,r.a.createElement(_,e,t.title),r.a.createElement(j,null,t.team))},I=n(76),L=n.n(I),N=n(433),T=function(){return r.a.createElement("svg",{width:"14",height:"14",viewBox:"0 0 14 14",xmlns:"http://www.w3.org/2000/svg"},r.a.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M8.41155 7.00032L13.6886 12.2822C14.079 12.6729 14.0787 13.306 13.688 13.6964C13.2973 14.0867 12.6641 14.0864 12.2738 13.6957L6.99628 8.41348L1.70658 13.6953C1.31577 14.0856 0.682602 14.0851 0.292368 13.6943C-0.0978661 13.3035 -0.0973954 12.6703 0.293419 12.2801L5.58271 6.99863L0.29565 1.70679C-0.094698 1.31609 -0.0944116 0.682921 0.296289 0.292573C0.68699 -0.0977741 1.32016 -0.0974878 1.7105 0.293213L6.99797 5.58547L12.2739 0.317343C12.6648 -0.0728905 13.2979 -0.0724199 13.6881 0.318395C14.0784 0.709209 14.0779 1.34237 13.6871 1.73261L8.41155 7.00032Z"}))},M=function(){return r.a.createElement("svg",{width:"15",height:"14",viewBox:"0 0 18 17",xmlns:"http://www.w3.org/2000/svg"},r.a.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M17 2H1C0.447715 2 0 1.55228 0 1C0 0.447715 0.447715 0 1 0H17C17.5523 0 18 0.447715 18 1C18 1.55228 17.5523 2 17 2ZM17 7H1C0.447715 7 0 6.55228 0 6C0 5.44772 0.447715 5 1 5H17C17.5523 5 18 5.44772 18 6C18 6.55228 17.5523 7 17 7ZM1 12H17C17.5523 12 18 11.5523 18 11C18 10.4477 17.5523 10 17 10H1C0.447715 10 0 10.4477 0 11C0 11.5523 0.447715 12 1 12ZM11 17H1C0.447715 17 0 16.5523 0 16C0 15.4477 0.447715 15 1 15H11C11.5523 15 12 15.4477 12 16C12 16.5523 11.5523 17 11 17Z"}))},O="#92929d",D=i.default.div.withConfig({displayName:"Description__Wrapper",componentId:"fb6sjd-0"})(["display:flex;svg{fill:",";}"],O),S=i.default.div.withConfig({displayName:"Description__ShapeWrapper",componentId:"fb6sjd-1"})(["width:20px;"]),R=i.default.div.withConfig({displayName:"Description__HeaderD",componentId:"fb6sjd-2"})(["display:flex;flex-direction:column;margin-bottom:10px;"]),H=i.default.span.withConfig({displayName:"Description__TitleD",componentId:"fb6sjd-3"})(["font-size:14px;letter-spacing:0.1px;color:#171725;margin:0 0 10px 10px;"]),Z=i.default.span.withConfig({displayName:"Description__TextD",componentId:"fb6sjd-4"})(["margin-left:10px;color:",";font-size:14px;"],O),z=function(e){var t=e.title;return r.a.createElement(D,null,r.a.createElement(S,null,r.a.createElement(M,null)),r.a.createElement(R,null,r.a.createElement(H,null,"Description"),r.a.createElement(Z,null,t,". Next Friday should be done. Next Monday we should deliver the first iteration. Make sure, we have a good result to be delivered by the day.")))},W="#92929d",A="#fc5a5a",B="#ffffff",F=i.default.section.withConfig({displayName:"TaskModal__Wrapper",componentId:"sc-10ab0nb-0"})(["position:absolute;top:0;width:100%;height:100vh;display:flex;align-items:center;justify-content:center;background-color:rgba(23,23,37,0.4);z-index:100;"]),J=i.default.div.withConfig({displayName:"TaskModal__Modal",componentId:"sc-10ab0nb-1"})(["display:flex;flex-direction:column;min-width:300px;width:30%;min-height:30vh;background-color:",";border-radius:20px;padding:20px 25px;"],B),P=i.default.div.withConfig({displayName:"TaskModal__Header",componentId:"sc-10ab0nb-2"})(["display:flex;justify-content:space-between;align-items:center;color:",";font-size:14px;height:50px;border-bottom:1px solid #e2e2ea;"],W),U=i.default.button.withConfig({displayName:"TaskModal__Button",componentId:"sc-10ab0nb-3"})(["background-color:",";border:none;outline:none;cursor:pointer;svg{fill:",";:hover{fill:#0062ff;}}"],B,W),q=i.default.div.withConfig({displayName:"TaskModal__Title",componentId:"sc-10ab0nb-4"})(["color:#171725;font-size:24px;margin:30px 0;"]),G=i.default.button.withConfig({displayName:"TaskModal__Delete",componentId:"sc-10ab0nb-5"})(["display:flex;justify-content:center;align-items:center;margin-top:30px;background-color:",";outline:none;cursor:pointer;color:",";height:38px;border-radius:20px;border:1px solid ",";:hover{color:",";background-color:",";}"],A,B,A,A,B),K={deleteTask:N.a},Q=Object(o.b)(null,K)((function(e){var t=e.type,n=e.title,a=e.onClose,o=e.id,i=e.deleteTask,l=document.getElementById("modal");return L.a.createPortal(r.a.createElement(F,null,r.a.createElement(J,null,r.a.createElement(P,null,r.a.createElement("span",null,t),r.a.createElement(U,{onClick:a},r.a.createElement(T,null))),r.a.createElement(q,null,r.a.createElement("span",null,n)),r.a.createElement(z,{title:n}),r.a.createElement(G,{onClick:function(){return function(e){i(e)}(o)}},"Delete"))),l)}));function V(){return(V=Object.assign||function(e){for(var t=1;tSquare Dashboard: React SPA -------------------------------------------------------------------------------- /dist/main~1f20a385.9c0f81488d5788f4d661.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[3],{31:function(n,t,e){"use strict";e.d(t,"a",(function(){return g})),e.d(t,"b",(function(){return x})),e.d(t,"d",(function(){return k})),e.d(t,"c",(function(){return d})),e.d(t,"f",(function(){return l})),e.d(t,"e",(function(){return h}));var o=e(10),r=e(179),i=e(180),a=e(27);function c(n){return"/"===n.charAt(0)?n:"/"+n}function s(n){return"/"===n.charAt(0)?n.substr(1):n}function f(n,t){return function(n,t){return 0===n.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(n.charAt(t.length))}(n,t)?n.substr(t.length):n}function u(n){return"/"===n.charAt(n.length-1)?n.slice(0,-1):n}function h(n){var t=n.pathname,e=n.search,o=n.hash,r=t||"/";return e&&"?"!==e&&(r+="?"===e.charAt(0)?e:"?"+e),o&&"#"!==o&&(r+="#"===o.charAt(0)?o:"#"+o),r}function d(n,t,e,i){var a;"string"==typeof n?(a=function(n){var t=n||"/",e="",o="",r=t.indexOf("#");-1!==r&&(o=t.substr(r),t=t.substr(0,r));var i=t.indexOf("?");return-1!==i&&(e=t.substr(i),t=t.substr(0,i)),{pathname:t,search:"?"===e?"":e,hash:"#"===o?"":o}}(n)).state=t:(void 0===(a=Object(o.a)({},n)).pathname&&(a.pathname=""),a.search?"?"!==a.search.charAt(0)&&(a.search="?"+a.search):a.search="",a.hash?"#"!==a.hash.charAt(0)&&(a.hash="#"+a.hash):a.hash="",void 0!==t&&void 0===a.state&&(a.state=t));try{a.pathname=decodeURI(a.pathname)}catch(n){throw n instanceof URIError?new URIError('Pathname "'+a.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):n}return e&&(a.key=e),i?a.pathname?"/"!==a.pathname.charAt(0)&&(a.pathname=Object(r.a)(a.pathname,i.pathname)):a.pathname=i.pathname:a.pathname||(a.pathname="/"),a}function l(n,t){return n.pathname===t.pathname&&n.search===t.search&&n.hash===t.hash&&n.key===t.key&&Object(i.a)(n.state,t.state)}function v(){var n=null;var t=[];return{setPrompt:function(t){return n=t,function(){n===t&&(n=null)}},confirmTransitionTo:function(t,e,o,r){if(null!=n){var i="function"==typeof n?n(t,e):n;"string"==typeof i?"function"==typeof o?o(i,r):r(!0):r(!1!==i)}else r(!0)},appendListener:function(n){var e=!0;function o(){e&&n.apply(void 0,arguments)}return t.push(o),function(){e=!1,t=t.filter((function(n){return n!==o}))}},notifyListeners:function(){for(var n=arguments.length,e=new Array(n),o=0;ot?e.splice(t,e.length-t,o):e.push(o),l({action:"PUSH",location:o,index:t,entries:e})}}))},replace:function(n,t){var o=d(n,t,p(),P.location);u.confirmTransitionTo(o,"REPLACE",e,(function(n){n&&(P.entries[P.index]=o,l({action:"REPLACE",location:o}))}))},go:y,goBack:function(){y(-1)},goForward:function(){y(1)},canGo:function(n){var t=P.index+n;return t>=0&&t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=Object(c.b)((function(e){return{auth:e.auth.isAuth}}))((function(e){var t=e.auth,n=e.children,r=f(e,["auth","children"]);return a.a.createElement(u.b,s({},r,{render:function(e){var r=e.location;return t?n:a.a.createElement(u.a,{to:{pathname:"/login",state:{from:r}}})}}))})),d=n(48),m=n(181),y=n.n(m),h=n(182),b=n.n(h);function g(){var e=function(e,t){t||(t=e.slice(0));return Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}(["\n @font-face {\n font-family: 'Poppins';\n src: url('","') format('woff');\n font-weight: 600;\n font-style: normal;\n }\n @font-face {\n font-family: 'Roboto';\n src: url('","') format('woff');\n font-weight: 600;\n font-style: normal;\n}\n body {\n font-family: 'Poppins', sans-serif;\n}\n"]);return g=function(){return e},e}var v=Object(d.createGlobalStyle)(g(),b.a,y.a),O=n(97),w=a.a.lazy((function(){return Promise.all([n.e(0),n.e(15)]).then(n.bind(null,468))})),j=a.a.lazy((function(){return Promise.all([n.e(0),n.e(20)]).then(n.bind(null,469))})),E=a.a.lazy((function(){return n.e(12).then(n.bind(null,474))})),P=a.a.lazy((function(){return Promise.all([n.e(0),n.e(13)]).then(n.bind(null,470))})),k=a.a.lazy((function(){return Promise.all([n.e(0),n.e(18)]).then(n.bind(null,471))})),S=a.a.lazy((function(){return Promise.all([n.e(0),n.e(17)]).then(n.bind(null,472))})),_=a.a.lazy((function(){return Promise.all([n.e(0),n.e(16)]).then(n.bind(null,473))})),A=function(){return a.a.createElement(a.a.Suspense,{fallback:a.a.createElement(l.a,null)},a.a.createElement(O.a,null,a.a.createElement(u.d,null,a.a.createElement(p,{exact:!0,path:"/"},a.a.createElement(w,null)),a.a.createElement(p,{path:"/messages"},a.a.createElement(_,null)),a.a.createElement(p,{path:"/tasks"},a.a.createElement(j,null)),a.a.createElement(p,{path:"/schedule"},a.a.createElement(S,null)),a.a.createElement(p,{path:"/activity"},a.a.createElement(P,null)),a.a.createElement(p,{path:"/settings"},a.a.createElement(k,null)),a.a.createElement(u.b,{path:"/login"},a.a.createElement(E,null)))),a.a.createElement(v,null))},x=n(183),T=n(172),D={isAuth:!1,login:"",password:""};var z=n(124);function C(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function I(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:D,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case T.a:return Object.assign({},{isAuth:!0,login:t.payload.login,password:t.payload.password});default:return e}},show:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:M,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case z.a:return I({},e,{list:I({},t.payload)});case z.b:return I({},e,{kanban:t.payload});default:return e}},tasks:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:W,t=arguments.length>1?arguments[1]:void 0,n=t.type,r=t.payload;switch(n){case N.b:var a=r.e.dataTransfer.getData("text/html"),o=R(a);return e.filter((function(e){return e.id===o&&(e.type=r.type),e}));case N.c:return[].concat(H(e),H(t.payload));case N.a:return e.filter((function(e){return e.id!==r}));default:return e}},teams:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:J,t=arguments.length>1?arguments[1]:void 0;switch(t.type){case B.a:return F({},e,{list:t.payload});default:return e}}});var V,X,Y=n(185),Z=(V=[x.a],X=q.applyMiddleware.apply(void 0,V),Object(q.createStore)(Q,Object(U.composeWithDevTools)(X)));i.a.render(a.a.createElement(c.a,{store:Z},a.a.createElement(Y.Normalize,null),a.a.createElement(A,null)),document.getElementById("root"))},96:function(e,t,n){"use strict";n.d(t,"a",(function(){return r})),n.d(t,"c",(function(){return a})),n.d(t,"b",(function(){return o}));var r="DELETE_TASK",a="FETCH_TASKS",o="DRAG_AND_DROP"}}); -------------------------------------------------------------------------------- /dist/main~253ae210.9c0f81488d5788f4d661.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[5],{10:function(e,t,r){"use strict";function n(){return(n=Object.assign||function(e){for(var t=1;t=0||(i[r]=e[r]);return i}r.d(t,"a",(function(){return n}))},36:function(e,t,r){"use strict";function n(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}r.d(t,"a",(function(){return n}))}}]); -------------------------------------------------------------------------------- /dist/main~31ecd969.9c0f81488d5788f4d661.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** @license React v0.18.0 2 | * scheduler.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | -------------------------------------------------------------------------------- /dist/main~7d359b94.9c0f81488d5788f4d661.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see main~7d359b94.9c0f81488d5788f4d661.js.LICENSE.txt */ 2 | (window.webpackJsonp=window.webpackJsonp||[]).push([[7],{126:function(e,t,n){"use strict";function r(e,t){if(e.length!==t.length)return!1;for(var n=0;n { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | Hi James, 54 | here’s your currently activity 55 | 56 | 57 | 58 | 59 | > 60 | ) 61 | } 62 | 63 | export default Activity 64 | -------------------------------------------------------------------------------- /src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import 'core-js/stable' 2 | import React from 'react' 3 | import 'regenerator-runtime/runtime' 4 | import Loader from 'components/Common/Loader' 5 | import PrivateRoute from 'components/Common/PrivateRoute' 6 | import { GlobalStyle } from 'components/Common/GlobalStyle' 7 | import { HashRouter as Router, Switch, Route } from 'react-router-dom' 8 | 9 | const Main = React.lazy(() => import('components/Main')) 10 | const Tasks = React.lazy(() => import('components/Tasks')) 11 | const Login = React.lazy(() => import('components/Login')) 12 | const Activity = React.lazy(() => import('components/Activity')) 13 | const Settings = React.lazy(() => import('components/Settings')) 14 | const Schedule = React.lazy(() => import('components/Schedule')) 15 | const Messages = React.lazy(() => import('components/Messages')) 16 | 17 | const App = () => { 18 | return ( 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 | export default App 51 | -------------------------------------------------------------------------------- /src/components/Common/Avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const WithAvatarWrapper = styled.img` 5 | width: ${(props: IAvatarProps) => props.size}px; 6 | height: ${(props: IAvatarProps) => props.size}px; 7 | cursor: not-allowed; 8 | border-radius: ${(props: IAvatarProps) => props.size}px; 9 | ` 10 | const WithoutAvatarWrapper = styled.div` 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | font-size: 14px; 15 | color: white; 16 | width: ${(props: IAvatarProps) => props.size}px; 17 | height: ${(props: IAvatarProps) => props.size}px; 18 | border-radius: ${(props: IAvatarProps) => props.size}px; 19 | cursor: not-allowed; 20 | background-color: ${(props: IAvatarProps) => props.color}; 21 | ` 22 | 23 | interface IAvatarProps { 24 | size: number 25 | name: string 26 | color: string 27 | avatar: string 28 | } 29 | 30 | const WithAvatar = (props: IAvatarProps) => { 31 | return 32 | } 33 | 34 | const WithoutAvatar = (props: IAvatarProps) => { 35 | return {props.name} 36 | } 37 | 38 | const Avatar: React.FC = props => { 39 | const { avatar } = props 40 | 41 | return ( 42 | <>{avatar ? : }> 43 | ) 44 | } 45 | 46 | export default Avatar 47 | -------------------------------------------------------------------------------- /src/components/Common/Buttons/Big/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const variables = { 5 | color: '#0062ff', 6 | crossSize: 15 7 | } 8 | 9 | const Wrapper = styled.div` 10 | width: 400px; 11 | min-width: 250px; 12 | height: 140px; 13 | border: 2px dashed ${variables.color}; 14 | border-radius: 20px; 15 | display: flex; 16 | justify-content: center; 17 | align-items: center; 18 | :hover { 19 | cursor: not-allowed; 20 | border: 2px dashed #d5d5dc; 21 | } 22 | ` 23 | const Button = styled.button` 24 | width: 38px; 25 | height: 38px; 26 | border-radius: 38px; 27 | background-color: white; 28 | border: none; 29 | position: relative; 30 | outline: none; 31 | ` 32 | const Cross = styled.div` 33 | width: ${variables.crossSize}px; 34 | height: ${variables.crossSize}px; 35 | :before, 36 | :after { 37 | position: absolute; 38 | left: 50%; 39 | content: ''; 40 | height: ${variables.crossSize}px; 41 | width: 2px; 42 | background-color: ${variables.color}; 43 | } 44 | :before { 45 | transform: rotate(90deg); 46 | } 47 | :after { 48 | transform: rotate(180deg); 49 | } 50 | ` 51 | const Text = styled.span` 52 | font-size: 14px; 53 | margin-left: 5px; 54 | ` 55 | 56 | interface IBigProps { 57 | name: string 58 | } 59 | 60 | const Big: React.FC = props => { 61 | const { name } = props 62 | 63 | return ( 64 | 65 | 66 | 67 | 68 | {name} 69 | 70 | ) 71 | } 72 | 73 | export default React.memo(Big) 74 | -------------------------------------------------------------------------------- /src/components/Common/Checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const variables = { 5 | greenColor: '#3dd598' 6 | } 7 | 8 | const CheckboxCustom = styled.span` 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | height: 14px; 13 | width: 14px; 14 | background-color: transparent; 15 | border-radius: 5px; 16 | border: 2px solid #b5b5be; 17 | ::after { 18 | position: absolute; 19 | content: ''; 20 | left: 2px; 21 | top: 2px; 22 | height: 0; 23 | width: 0; 24 | border-radius: 5px; 25 | border: solid white; 26 | border-width: 0 3px 3px 0; 27 | opacity: 1; 28 | } 29 | ` 30 | const CheckboxLabel = styled.label` 31 | display: flex; 32 | position: relative; 33 | cursor: pointer; 34 | font-size: 14px; 35 | letter-spacing: 0.1px; 36 | color: #696974; 37 | margin: 15px 0 0 10px; 38 | input { 39 | position: absolute; 40 | opacity: 0; 41 | cursor: pointer; 42 | } 43 | input:checked ~ ${CheckboxCustom} { 44 | background-color: ${variables.greenColor}; 45 | border-radius: 5px; 46 | opacity: 1; 47 | border: 2px solid ${variables.greenColor}; 48 | } 49 | input:checked ~ ${CheckboxCustom}::after { 50 | transform: rotate(45deg) scale(1); 51 | opacity: 1; 52 | left: 5px; 53 | top: 2px; 54 | width: 3px; 55 | height: 6px; 56 | border: solid white; 57 | border-width: 0 2px 2px 0; 58 | background-color: transparent; 59 | border-radius: 0; 60 | } 61 | ` 62 | const Text = styled.span` 63 | margin: 0 0 0 30px; 64 | ` 65 | 66 | interface IContentTitleProps { 67 | name: string 68 | value: string 69 | checked: boolean 70 | handleCheckbox?: (e: React.ChangeEvent) => void 71 | } 72 | 73 | const Checkbox: React.FC = props => { 74 | const { name, value, checked, handleCheckbox } = props 75 | 76 | return ( 77 | <> 78 | 79 | 86 | {value} 87 | 88 | 89 | > 90 | ) 91 | } 92 | 93 | export default Checkbox 94 | -------------------------------------------------------------------------------- /src/components/Common/ErrorBoundary/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Wrapper = styled.div` 5 | width: 100%; 6 | height: auto; 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | justify-content: center; 11 | ` 12 | const Title = styled.span` 13 | color: #92929d; 14 | ` 15 | const Details = styled.details` 16 | margin-top: 10px; 17 | color: #92929d; 18 | white-space: pre-wrap; 19 | font-size: 10px; 20 | ` 21 | 22 | class ErrorBoundary extends React.Component { 23 | constructor(props) { 24 | super(props) 25 | this.state = { error: null, errorInfo: null } 26 | } 27 | 28 | componentDidCatch(error, errorInfo) { 29 | this.setState({ 30 | error: error, 31 | errorInfo: errorInfo 32 | }) 33 | } 34 | 35 | render() { 36 | if (this.state.errorInfo) { 37 | return ( 38 | 39 | Oops, something went wrong 40 | 41 | {this.state.error && this.state.error.toString()} 42 | 43 | {this.state.errorInfo.componentStack} 44 | 45 | 46 | ) 47 | } 48 | return this.props.children 49 | } 50 | } 51 | 52 | export default ErrorBoundary 53 | -------------------------------------------------------------------------------- /src/components/Common/GlobalStyle/index.jsx: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components' 2 | import Roboto from 'assets/fonts/Roboto/Roboto-Regular.woff' 3 | import Poppins from 'assets/fonts/Poppins/Poppins-Regular.woff' 4 | 5 | export const GlobalStyle = createGlobalStyle` 6 | @font-face { 7 | font-family: 'Poppins'; 8 | src: url('${Poppins}') format('woff'); 9 | font-weight: 600; 10 | font-style: normal; 11 | } 12 | @font-face { 13 | font-family: 'Roboto'; 14 | src: url('${Roboto}') format('woff'); 15 | font-weight: 600; 16 | font-style: normal; 17 | } 18 | body { 19 | font-family: 'Poppins', sans-serif; 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /src/components/Common/Header/Actions/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const variables = { 5 | color: '#0062ff', 6 | crossSize: 18, 7 | colorWhite: '#fff' 8 | } 9 | 10 | const Cross = styled.div` 11 | position: absolute; 12 | left: 20px; 13 | top: 10px; 14 | width: ${variables.crossSize}px; 15 | height: ${variables.crossSize}px; 16 | :before, 17 | :after { 18 | position: absolute; 19 | left: 0; 20 | content: ''; 21 | height: ${variables.crossSize}px; 22 | width: 2px; 23 | background-color: ${variables.colorWhite}; 24 | } 25 | :before { 26 | transform: rotate(90deg); 27 | } 28 | :after { 29 | transform: rotate(180deg); 30 | } 31 | ` 32 | const Wrapper = styled.button` 33 | position: relative; 34 | display: flex; 35 | align-items: center; 36 | justify-content: space-around; 37 | background-color: ${variables.color}; 38 | border-radius: 10px; 39 | width: 86px; 40 | height: 38px; 41 | border: none; 42 | cursor: not-allowed; 43 | outline: none; 44 | color: ${variables.colorWhite}; 45 | font-size: 14px; 46 | :hover { 47 | background-color: ${variables.colorWhite}; 48 | border: 1px solid ${variables.color}; 49 | color: ${variables.color}; 50 | } 51 | :hover ${Cross}:before { 52 | background-color: ${variables.color}; 53 | } 54 | :hover ${Cross}:after { 55 | background-color: ${variables.color}; 56 | } 57 | @media (max-width: 450px) { 58 | display: none; 59 | } 60 | ` 61 | 62 | const Button = () => { 63 | return ( 64 | 65 | 66 | 67 | 68 | New 69 | 70 | ) 71 | } 72 | 73 | export default Button 74 | -------------------------------------------------------------------------------- /src/components/Common/Header/Actions/Search/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Wrapper = styled.div` 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-around; 8 | width: 86px; 9 | height: 38px; 10 | @media (max-width: 450px) { 11 | display: none; 12 | } 13 | ` 14 | const Input = styled.input` 15 | width: 86px; 16 | height: 38px; 17 | background: #f1f1f5; 18 | border-radius: 10px; 19 | transition: width 0.3s; 20 | font-size: 14px; 21 | text-align: center; 22 | line-height: 38px; 23 | color: #696974; 24 | border: 1px dashed #0062ff; 25 | :focus { 26 | width: 120px; 27 | outline: none; 28 | padding: 0 10px; 29 | } 30 | ` 31 | 32 | const Search = () => { 33 | return ( 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default Search 41 | -------------------------------------------------------------------------------- /src/components/Common/Header/Actions/Shape/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Avatar from 'components/Common/Avatar' 4 | 5 | const variables = { 6 | size: 12 7 | } 8 | 9 | const Notification = styled.div` 10 | background-color: #fc5a5a; 11 | width: ${variables.size}px; 12 | height: ${variables.size}px; 13 | border-radius: ${variables.size}px; 14 | position: absolute; 15 | right: -4px; 16 | top: -6px; 17 | opacity: 1; 18 | transition: opacity 0.4s; 19 | ` 20 | const ShapeInfo = styled.div` 21 | z-index: 1; 22 | width: 200px; 23 | display: none; 24 | flex-direction: column; 25 | position: absolute; 26 | top: 35px; 27 | right: -50px; 28 | background: #ffffff; 29 | border: 1px solid #f1f1f5; 30 | box-shadow: 0 5px 15px rgba(68, 68, 79, 0.1); 31 | border-radius: 8px; 32 | color: #696974; 33 | font-size: 12px; 34 | padding: 15px; 35 | ` 36 | const Text = styled.span` 37 | font-style: italic; 38 | margin: 10px 0 0 0; 39 | ` 40 | const Wrapper = styled.div` 41 | display: flex; 42 | justify-content: center; 43 | align-items: center; 44 | position: relative; 45 | cursor: pointer; 46 | :hover ${Notification} { 47 | opacity: 0; 48 | transition: opacity 0.4s; 49 | } 50 | :hover ${ShapeInfo} { 51 | display: flex; 52 | } 53 | ` 54 | const AvatarWrapper = styled.div` 55 | display: flex; 56 | width: 25px; 57 | align-items: center; 58 | span { 59 | margin: 0 0 0 10px; 60 | } 61 | ` 62 | 63 | const userProps = { 64 | size: 25, 65 | name: 'TE', 66 | color: '', 67 | avatar: require('assets/images/james.png') 68 | } 69 | 70 | const textNotification = `React can also render on the server using Node and power mobile apps using React Native` 71 | const ShapeIcon = (): object => { 72 | return ( 73 | 80 | 86 | 87 | ) 88 | } 89 | 90 | const Shape = () => { 91 | const notification = true 92 | 93 | return ( 94 | 95 | <> 96 | 97 | 98 | 99 | @MikeCobain: 100 | 101 | {textNotification} 102 | 103 | {notification && } 104 | {ShapeIcon()} 105 | > 106 | 107 | ) 108 | } 109 | 110 | export default Shape 111 | -------------------------------------------------------------------------------- /src/components/Common/Header/Actions/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Avatar from 'components/Common/Avatar' 4 | import ActionsShape from 'components/Common/Header/Actions/Shape' 5 | import ActionsSearch from 'components/Common/Header/Actions/Search' 6 | import ActionsButton from 'components/Common/Header/Actions/Button' 7 | 8 | const Wrapper = styled.div` 9 | display: grid; 10 | grid-gap: 10px; 11 | justify-items: center; 12 | align-items: center; 13 | grid-template-columns: 3fr 3fr 1fr 1fr; 14 | @media (max-width: 450px) { 15 | grid-template-columns: 1fr 1fr; 16 | justify-items: end; 17 | } 18 | ` 19 | 20 | const userProps = { 21 | size: 32, 22 | name: 'KA', 23 | color: '', 24 | avatar: require('assets/images/james.png') 25 | } 26 | 27 | const Actions = () => { 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | export default Actions 39 | -------------------------------------------------------------------------------- /src/components/Common/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { Link } from 'react-router-dom' 4 | import Logo from 'components/Common/Logo' 5 | import Actions from 'components/Common/Header/Actions' 6 | 7 | const Wrapper = styled.section` 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | height: 70px; 12 | max-width: 1600px; 13 | margin: 0 auto; 14 | box-shadow: inset 0px -1px 0px #e2e2ea; 15 | ` 16 | const LogoWrapper = styled.div` 17 | margin-left: 25px; 18 | a { 19 | text-decoration: none; 20 | } 21 | ` 22 | const ActionsWrapper = styled.div` 23 | margin-right: 25px; 24 | ` 25 | 26 | const logoProps = { 27 | title: { 28 | size: 20, 29 | color: '#44444F', 30 | text: 'Square' 31 | }, 32 | image: { 33 | size: 34, 34 | color: '#0062ff' 35 | } 36 | } 37 | 38 | const Header = () => { 39 | return ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | export default Header 54 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Common/Attach.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Attach = () => { 4 | return ( 5 | 12 | 18 | 19 | ) 20 | } 21 | 22 | export default Attach 23 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Common/Close.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Close = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default Close 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Common/Oval.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconOval = () => { 4 | return ( 5 | 12 | 18 | 19 | ) 20 | } 21 | 22 | export default IconOval 23 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Common/Shape.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Shape = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default Shape 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Menu/Activity.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconActivity = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default IconActivity 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Menu/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconDashboard = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default IconDashboard 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Menu/Messages.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconMessages = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default IconMessages 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Menu/Schedule.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconSchedule = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default IconSchedule 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Menu/Settings.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconSettings = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default IconSettings 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Menu/Tasks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconTasks = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default IconTasks 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/SocialMedia/Facebook.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconFacebook = () => { 4 | return ( 5 | 12 | 16 | 20 | 21 | ) 22 | } 23 | 24 | export default IconFacebook 25 | -------------------------------------------------------------------------------- /src/components/Common/Icons/SocialMedia/Google.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const IconGoogle = () => { 4 | return ( 5 | 12 | 20 | 26 | 27 | 28 | 32 | 33 | 41 | 47 | 48 | 49 | 53 | 54 | 62 | 68 | 69 | 70 | 74 | 75 | 83 | 89 | 90 | 91 | 95 | 96 | 97 | ) 98 | } 99 | 100 | export default IconGoogle 101 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Switcher/Default.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Default = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default Default 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Switcher/Gantt.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Gantt = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default Gantt 21 | -------------------------------------------------------------------------------- /src/components/Common/Icons/Switcher/Kanban.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Kanban = () => { 4 | return ( 5 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default Kanban 21 | -------------------------------------------------------------------------------- /src/components/Common/Loader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const variables = { 5 | color: '#0062ff' 6 | } 7 | 8 | const Wrapper = styled.div` 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | height: ${(props: ILoaderProps) => 13 | props.height === 'none' ? 'none' : '98vh'}; 14 | margin: 0 auto; 15 | ` 16 | const Svg = styled.svg` 17 | @keyframes rotate { 18 | 0% { 19 | transform: rotate(0deg); 20 | } 21 | 100% { 22 | transform: rotate(270deg); 23 | } 24 | } 25 | animation: rotate 1.4s linear infinite; 26 | ` 27 | const Circle = styled.circle` 28 | @keyframes colors { 29 | 0% { 30 | stroke: ${variables.color}; 31 | } 32 | 100% { 33 | stroke: ${variables.color}; 34 | } 35 | } 36 | @keyframes dash { 37 | 0% { 38 | stroke-dashoffset: 187; 39 | } 40 | 50% { 41 | stroke-dashoffset: 46.75; 42 | transform: rotate(135deg); 43 | } 44 | 100% { 45 | stroke-dashoffset: 187; 46 | transform: rotate(450deg); 47 | } 48 | } 49 | stroke-dasharray: 187; 50 | stroke-dashoffset: 0; 51 | transform-origin: center; 52 | animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite; 53 | ` 54 | 55 | interface ILoaderProps { 56 | height?: string 57 | } 58 | 59 | const Loader: React.FC = props => { 60 | return ( 61 | 62 | 68 | 76 | 77 | 78 | ) 79 | } 80 | 81 | export default Loader 82 | -------------------------------------------------------------------------------- /src/components/Common/Logo/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface ILogoImageProps { 4 | size: number 5 | color: string 6 | } 7 | 8 | const Image: React.FC = props => { 9 | const { size, color } = props 10 | 11 | return ( 12 | 19 | 28 | 37 | 38 | ) 39 | } 40 | 41 | export default Image 42 | -------------------------------------------------------------------------------- /src/components/Common/Logo/Title/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Wrapper = styled.span` 5 | font-size: ${(props: ILogoTitleProps) => props.size}px; 6 | color: ${(props: ILogoTitleProps) => props.color}; 7 | font-weight: 900; 8 | margin-left: 5px; 9 | ` 10 | 11 | interface ILogoTitleProps { 12 | size: number 13 | color: string 14 | text: string 15 | } 16 | 17 | const Title: React.FC = props => { 18 | const { text } = props 19 | 20 | return {text} 21 | } 22 | 23 | export default Title 24 | -------------------------------------------------------------------------------- /src/components/Common/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import LogoImage from 'components/Common/Logo/Image' 4 | import LogoTitle from 'components/Common/Logo/Title' 5 | 6 | const Section = styled.section` 7 | display: flex; 8 | align-items: center; 9 | justify-content: space-between; 10 | ` 11 | 12 | interface ILogoProps { 13 | title: { 14 | size: number 15 | color: string 16 | text: string 17 | } 18 | image: { 19 | size: number 20 | color: string 21 | } 22 | } 23 | 24 | const Logo = (props: ILogoProps) => { 25 | const { image, title } = props 26 | 27 | return ( 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | export default Logo 36 | -------------------------------------------------------------------------------- /src/components/Common/PrivateRoute/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import { Route, Redirect, RouteProps } from 'react-router-dom' 5 | 6 | interface IPrivateRouteProps extends RouteProps { 7 | auth: boolean 8 | } 9 | 10 | function PrivateRoute({ auth, children, ...rest }: IPrivateRouteProps) { 11 | return ( 12 | 15 | auth ? ( 16 | children 17 | ) : ( 18 | 24 | ) 25 | } 26 | /> 27 | ) 28 | } 29 | 30 | const mapStateToProps = (state: AppState) => { 31 | return { 32 | auth: state.auth.isAuth 33 | } 34 | } 35 | 36 | export default connect(mapStateToProps)(PrivateRoute) 37 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/Header/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const variables = { 5 | color: '#0062ff', 6 | crossSize: 7 7 | } 8 | 9 | const Wrapper = styled.div` 10 | display: flex; 11 | margin-top: 30px; 12 | ` 13 | const TextButton = styled.button` 14 | display: flex; 15 | align-items: center; 16 | position: relative; 17 | font-size: 12px; 18 | letter-spacing: 0.8px; 19 | text-transform: uppercase; 20 | color: ${variables.color}; 21 | border: none; 22 | cursor: not-allowed; 23 | background-color: white; 24 | :focus { 25 | outline: none; 26 | } 27 | ` 28 | const Cross = styled.div` 29 | width: ${variables.crossSize}px; 30 | height: ${variables.crossSize}px; 31 | :before, 32 | :after { 33 | position: absolute; 34 | left: 2px; 35 | content: ''; 36 | height: ${variables.crossSize}px; 37 | width: 1px; 38 | background-color: ${variables.color}; 39 | } 40 | :before { 41 | transform: rotate(90deg); 42 | } 43 | :after { 44 | transform: rotate(180deg); 45 | } 46 | ` 47 | 48 | const Button = () => { 49 | return ( 50 | 51 | 52 | 53 | Add new team 54 | 55 | 56 | ) 57 | } 58 | 59 | export default Button 60 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/Header/Items/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import HeaderTeams from 'components/Common/Sidebar/Header/Teams' 4 | import HeaderButton from 'components/Common/Sidebar/Header/Button' 5 | 6 | const variables = { 7 | color: '#92929d' 8 | } 9 | 10 | const Wrapper = styled.div` 11 | display: flex; 12 | flex-direction: column; 13 | ` 14 | const Block = styled.div` 15 | display: flex; 16 | justify-content: space-between; 17 | ` 18 | const Arrow = styled.div` 19 | border: solid ${variables.color}; 20 | border-width: 0 2px 2px 0; 21 | display: flex; 22 | padding: 3px; 23 | cursor: pointer; 24 | ` 25 | const ArrowDown = styled(Arrow)` 26 | transform: rotate(45deg); 27 | ` 28 | const ArrowUp = styled(Arrow)` 29 | transform: rotate(135deg); 30 | ` 31 | const TeamsTitle = styled.span` 32 | text-transform: uppercase; 33 | font-size: 14px; 34 | color: ${variables.color}; 35 | letter-spacing: 1px; 36 | ` 37 | 38 | const Items = () => { 39 | const [opened, setOpened] = React.useState(true) 40 | 41 | const handleOpened = (): void => { 42 | setOpened(prevState => !prevState) 43 | } 44 | 45 | return ( 46 | 47 | 48 | Teams 49 | {opened ? : } 50 | 51 | {opened && } 52 | 53 | 54 | ) 55 | } 56 | 57 | export default Items 58 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/Header/Teams/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Avatar from 'components/Common/Avatar' 4 | 5 | const Wrapper = styled.div` 6 | display: flex; 7 | align-items: center; 8 | margin: 15px 0 20px 0; 9 | cursor: not-allowed; 10 | ` 11 | const TeamAvatar = styled.img` 12 | width: 32px; 13 | height: 32px; 14 | margin-right: 10px; 15 | ` 16 | const TeamName = styled.span` 17 | font-size: 14px; 18 | letter-spacing: 0.1px; 19 | color: #171725; 20 | ` 21 | const Users = styled.div` 22 | display: grid; 23 | grid-gap: 10px; 24 | justify-items: center; 25 | align-items: center; 26 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; 27 | ` 28 | 29 | const usersData = [ 30 | { 31 | size: 26, 32 | name: 'KA', 33 | color: '#FFC542', 34 | avatar: '' 35 | }, 36 | { 37 | size: 26, 38 | name: 'TE', 39 | color: '', 40 | avatar: require('assets/images/james.png') 41 | }, 42 | { 43 | size: 26, 44 | name: 'AS', 45 | color: '#A461D8', 46 | avatar: '' 47 | }, 48 | { 49 | size: 26, 50 | name: 'GU', 51 | color: '#FF9AD5', 52 | avatar: '' 53 | }, 54 | { 55 | size: 26, 56 | name: 'ZT', 57 | color: '#82C43C', 58 | avatar: '' 59 | }, 60 | { 61 | size: 26, 62 | name: 'MI', 63 | color: '#50B5FF', 64 | avatar: '' 65 | } 66 | ] 67 | 68 | interface IUserProps { 69 | size: number 70 | name: string 71 | color: string 72 | avatar: string 73 | } 74 | 75 | const users = usersData.map((user: IUserProps, idx: number): object => ( 76 | 77 | )) 78 | 79 | const Teams = () => { 80 | return ( 81 | <> 82 | 83 | 87 | Iconspace Team 88 | 89 | {users} 90 | > 91 | ) 92 | } 93 | 94 | export default Teams 95 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import HeaderItems from 'components/Common/Sidebar/Header/Items' 4 | 5 | const Wrapper = styled.div` 6 | height: 230px; 7 | padding: 20px 20px 10px 20px; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | @media (max-width: 620px) { 12 | display: none; 13 | } 14 | ` 15 | const Main = styled.div` 16 | height: 190px; 17 | width: 230px; 18 | border-bottom: 1px solid #f1f1f5; 19 | ` 20 | 21 | const Header = () => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | export default Header 32 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/Menu/Item/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { NavLink } from 'react-router-dom' 4 | 5 | const variables = { 6 | blueColor: '#0062ff', 7 | grayColor: '#92929d' 8 | } 9 | 10 | const active = 'nav-item-active' 11 | const NavItem = styled(NavLink).attrs({ 12 | active 13 | })` 14 | display: flex; 15 | align-items: center; 16 | text-decoration: none; 17 | color: #171725; 18 | font-size: 14px; 19 | letter-spacing: 0.1px; 20 | border-left: 3px solid #fff; 21 | svg { 22 | fill: ${variables.grayColor}; 23 | } 24 | &.${active} { 25 | color: ${variables.blueColor}; 26 | border-left: 3px solid ${variables.blueColor}; 27 | svg { 28 | fill: ${variables.blueColor}; 29 | } 30 | } 31 | ` 32 | const Wrapper = styled.div` 33 | display: flex; 34 | align-items: center; 35 | margin-bottom: 40px; 36 | ` 37 | const Icon = styled.div` 38 | margin: 0 24px; 39 | ` 40 | const NameLink = styled.span` 41 | @media (max-width: 620px) { 42 | display: none; 43 | } 44 | ` 45 | 46 | interface IItemProps { 47 | name: string 48 | icon: object | string 49 | link: string 50 | } 51 | 52 | const Item: React.FC = props => { 53 | const { icon, name, link } = props 54 | 55 | return ( 56 | 57 | 58 | {icon} 59 | {name} 60 | 61 | 62 | ) 63 | } 64 | 65 | export default Item 66 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Item from 'components/Common/Sidebar/Menu/Item' 4 | import IconTasks from 'components/Common/Icons/Menu/Tasks' 5 | import ErrorBoundary from 'components/Common/ErrorBoundary' 6 | import IconMessages from 'components/Common/Icons/Menu/Messages' 7 | import IconSchedule from 'components/Common/Icons/Menu/Schedule' 8 | import IconActivity from 'components/Common/Icons/Menu/Activity' 9 | import IconSettings from 'components/Common/Icons/Menu/Settings' 10 | import IconDashboard from 'components/Common/Icons/Menu/Dashboard' 11 | 12 | const Wrapper = styled.nav` 13 | display: flex; 14 | flex-direction: column; 15 | @media (max-width: 620px) { 16 | margin-top: 40px; 17 | } 18 | ` 19 | 20 | const itemsData = [ 21 | { 22 | name: 'Dashboard', 23 | icon: IconDashboard(), 24 | link: '/' 25 | }, 26 | { 27 | name: 'Messages', 28 | icon: IconMessages(), 29 | link: '/messages' 30 | }, 31 | { 32 | name: 'Tasks', 33 | icon: IconTasks(), 34 | link: '/tasks' 35 | }, 36 | { 37 | name: 'Schedule', 38 | icon: IconSchedule(), 39 | link: '/schedule' 40 | }, 41 | { 42 | name: 'Activity', 43 | icon: IconActivity(), 44 | link: '/activity' 45 | }, 46 | { 47 | name: 'Settings', 48 | icon: IconSettings(), 49 | link: '/settings' 50 | } 51 | ] 52 | 53 | interface IItemProps { 54 | name: string 55 | icon: object | string 56 | link: string 57 | } 58 | 59 | const items = itemsData.map((item: IItemProps, idx: number): object => ( 60 | 61 | )) 62 | 63 | const Menu = () => { 64 | return ( 65 | 66 | {items} 67 | 68 | ) 69 | } 70 | 71 | export default Menu 72 | -------------------------------------------------------------------------------- /src/components/Common/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Menu from 'components/Common/Sidebar/Menu' 4 | import Header from 'components/Common/Sidebar/Header' 5 | 6 | const Wrapper = styled.section` 7 | display: flex; 8 | flex-direction: column; 9 | max-width: 250px; 10 | height: 92vh; 11 | min-height: 640px; 12 | ` 13 | 14 | const Sidebar = () => { 15 | return ( 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default Sidebar 24 | -------------------------------------------------------------------------------- /src/components/Common/Task/Info/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { ITaskState } from 'store/tasks/types' 4 | import TasksIcon from 'components/Common/Icons/Menu/Tasks' 5 | import AttachIcon from 'components/Common/Icons/Common/Attach' 6 | import ActivityIcon from 'components/Common/Icons/Menu/Activity' 7 | 8 | const TextStyles = styled.div` 9 | font-size: 14px; 10 | letter-spacing: 0.1px; 11 | color: #92929d; 12 | ` 13 | const Wrapper = styled.div` 14 | display: flex; 15 | margin: 15px 0 10px 0; 16 | ` 17 | const Attach = styled(TextStyles)` 18 | display: flex; 19 | align-items: center; 20 | svg { 21 | margin-right: 5px; 22 | } 23 | ` 24 | const Status = styled(TextStyles)` 25 | margin: 0 15px 0 20px; 26 | display: flex; 27 | align-items: center; 28 | svg { 29 | margin-right: 5px; 30 | fill: #92929d; 31 | width: 14px; 32 | height: 14px; 33 | } 34 | ` 35 | const Activity = styled(Status)` 36 | background-color: ${(props: IInfoProps) => props.data.score.colors.bg}; 37 | color: ${(props: IInfoProps) => props.data.score.colors.text}; 38 | padding: 5px; 39 | border-radius: 5px; 40 | margin: 0; 41 | span:last-child { 42 | margin-left: 5px; 43 | } 44 | svg { 45 | fill: ${(props: IInfoProps) => props.data.score.colors.text}; 46 | } 47 | ` 48 | 49 | interface IInfoProps { 50 | data: ITaskState 51 | } 52 | 53 | const Info: React.FC = props => { 54 | const { data } = props 55 | 56 | return ( 57 | 58 | 59 | 60 | {data.attach} 61 | 62 | 63 | 64 | {data.status} 65 | 66 | {data.score.days > 0 && ( 67 | 68 | 69 | {data.score.days} 70 | days left 71 | 72 | )} 73 | 74 | ) 75 | } 76 | 77 | export default Info 78 | -------------------------------------------------------------------------------- /src/components/Common/Task/Score/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import { ITaskState } from 'store/tasks/types' 6 | import { getKanbanOption } from 'store/show/selectors' 7 | 8 | const Wrapper = styled.div` 9 | display: flex; 10 | flex-direction: ${(props: IScoreProps) => 11 | props.option ? 'column' : 'row-reverse'}; 12 | align-items: ${(props: IScoreProps) => !props.option && 'center'}; 13 | ` 14 | const ScoreLine = styled.div` 15 | background-color: #e2e2ea; 16 | width: 100%; 17 | height: 3px; 18 | border-radius: 2.5px; 19 | min-width: ${(props: IScoreProps) => !props.option && '150px'}; 20 | div { 21 | height: 3px; 22 | background-color: #3dd598; 23 | width: ${(props: IScoreProps) => `${props.data.line}%`}; 24 | } 25 | ` 26 | const ScoreLineTitle = styled.div` 27 | font-size: 14px; 28 | letter-spacing: 0.1px; 29 | color: #696974; 30 | display: flex; 31 | justify-content: flex-end; 32 | width: 100%; 33 | margin-left: ${(props: IScoreProps) => !props.option && '10px'}; 34 | ` 35 | 36 | interface IScoreProps { 37 | data: ITaskState 38 | option: boolean 39 | } 40 | 41 | const Score: React.FC = props => { 42 | const { data } = props 43 | 44 | return ( 45 | 46 | {data.line}% 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | 54 | const mapStateToProps = (state: AppState) => { 55 | return { 56 | option: getKanbanOption(state) 57 | } 58 | } 59 | 60 | export default connect(mapStateToProps)(Score) 61 | -------------------------------------------------------------------------------- /src/components/Common/Task/Titles/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { ITaskState } from 'store/tasks/types' 4 | 5 | const TextStyles = styled.div` 6 | font-size: 14px; 7 | letter-spacing: 0.1px; 8 | color: #92929d; 9 | ` 10 | const Wrapper = styled.div` 11 | display: flex; 12 | flex-direction: column; 13 | ` 14 | const Title = styled(TextStyles)` 15 | color: #171725; 16 | margin-bottom: 7px; 17 | text-decoration: ${(props: ITitleProps) => 18 | props.data.score.days === 0 && 'line-through'}; 19 | ` 20 | const Team = styled(TextStyles)` 21 | color: #696974; 22 | ` 23 | 24 | interface ITitleProps { 25 | data: ITaskState 26 | } 27 | 28 | const Titles: React.FC = props => { 29 | const { data } = props 30 | 31 | return ( 32 | 33 | {data.title} 34 | {data.team} 35 | 36 | ) 37 | } 38 | 39 | export default Titles 40 | -------------------------------------------------------------------------------- /src/components/Common/Task/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import Avatar from 'components/Common/Avatar' 6 | import Info from 'components/Common/Task/Info' 7 | import { ITaskState } from 'store/tasks/types' 8 | import Score from 'components/Common/Task/Score' 9 | import Titles from 'components/Common/Task/Titles' 10 | import TaskModal from 'components/Common/TaskModal' 11 | import { ITeamListUserState } from 'store/teams/types' 12 | import { getKanbanOption } from 'store/show/selectors' 13 | 14 | const Wrapper = styled.div` 15 | display: flex; 16 | justify-content: ${(props: ITaskProps) => !props.option && 'space-around'}; 17 | flex-direction: ${(props: ITaskProps) => (props.option ? 'column' : 'row')}; 18 | cursor: move; 19 | border-radius: 20px; 20 | padding: 15px; 21 | margin: 0 5px 10px 5px; 22 | background: ${props => 23 | props.drag 24 | ? `repeating-linear-gradient( 25 | 45deg, 26 | white, 27 | white 5px, 28 | #f1f1f5 5px, 29 | #f1f1f5 10px 30 | )` 31 | : 'white'}; 32 | border: ${props => (props.drag ? '1px dashed #92929d' : '1px dashed white')}; 33 | opacity: ${props => (props.drag ? '0.999' : '1')}; 34 | ` 35 | const Users = styled.div` 36 | display: grid; 37 | grid-template-columns: repeat(6, 1fr); 38 | grid-gap: 10px; 39 | margin: 10px 0 0 0; 40 | ` 41 | 42 | interface ITaskProps { 43 | data: ITaskState 44 | key: string 45 | option: boolean 46 | drag?: boolean 47 | } 48 | 49 | const Task: React.FC = props => { 50 | const { data } = props 51 | 52 | const [modal, setModal] = React.useState(false) 53 | const [drag, setDrag] = React.useState(false) 54 | 55 | const onDragStart = (e: React.DragEvent): void => { 56 | setDrag(prevState => !prevState) 57 | e.dataTransfer.setData('text/html', data.id) 58 | } 59 | 60 | const toggleModal = (): void => { 61 | setModal(prevState => !prevState) 62 | } 63 | 64 | const users = data.users.map( 65 | (user: ITeamListUserState, idx: number): object => ( 66 | 67 | ) 68 | ) 69 | 70 | return ( 71 | <> 72 | 79 | 80 | 81 | 82 | {users} 83 | 84 | <>{modal && }> 85 | > 86 | ) 87 | } 88 | const mapStateToProps = (state: AppState) => { 89 | return { 90 | option: getKanbanOption(state) 91 | } 92 | } 93 | 94 | export default connect(mapStateToProps)(Task) 95 | -------------------------------------------------------------------------------- /src/components/Common/TaskModal/Description/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Shape from 'components/Common/Icons/Common/Shape' 4 | 5 | const variables = { 6 | colorGray: '#92929d' 7 | } 8 | const Wrapper = styled.div` 9 | display: flex; 10 | svg { 11 | fill: ${variables.colorGray}; 12 | } 13 | ` 14 | const ShapeWrapper = styled.div` 15 | width: 20px; 16 | ` 17 | const HeaderD = styled.div` 18 | display: flex; 19 | flex-direction: column; 20 | margin-bottom: 10px; 21 | ` 22 | const TitleD = styled.span` 23 | font-size: 14px; 24 | letter-spacing: 0.1px; 25 | color: #171725; 26 | margin: 0 0 10px 10px; 27 | ` 28 | const TextD = styled.span` 29 | margin-left: 10px; 30 | color: ${variables.colorGray}; 31 | font-size: 14px; 32 | ` 33 | interface IDescriptionProps { 34 | title: string 35 | } 36 | 37 | const Description: React.FC = props => { 38 | const { title } = props 39 | 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | Description 47 | 48 | {title}. Next Friday should be done. Next Monday we should deliver the 49 | first iteration. Make sure, we have a good result to be delivered by 50 | the day. 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | export default Description 58 | -------------------------------------------------------------------------------- /src/components/Common/TaskModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import { ITaskState } from 'store/tasks/types' 6 | import { deleteTask } from 'store/tasks/actions' 7 | import Close from 'components/Common/Icons/Common/Close' 8 | import Description from 'components/Common/TaskModal/Description' 9 | 10 | const variables = { 11 | colorGray: '#92929d', 12 | colorRed: '#fc5a5a', 13 | colorWhite: '#ffffff' 14 | } 15 | 16 | const Wrapper = styled.section` 17 | position: absolute; 18 | top: 0; 19 | width: 100%; 20 | height: 100vh; 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | background-color: rgba(23, 23, 37, 0.4); 25 | z-index: 100; 26 | ` 27 | const Modal = styled.div` 28 | display: flex; 29 | flex-direction: column; 30 | min-width: 300px; 31 | width: 30%; 32 | min-height: 30vh; 33 | background-color: ${variables.colorWhite}; 34 | border-radius: 20px; 35 | padding: 20px 25px; 36 | ` 37 | const Header = styled.div` 38 | display: flex; 39 | justify-content: space-between; 40 | align-items: center; 41 | color: ${variables.colorGray}; 42 | font-size: 14px; 43 | height: 50px; 44 | border-bottom: 1px solid #e2e2ea; 45 | ` 46 | const Button = styled.button` 47 | background-color: ${variables.colorWhite}; 48 | border: none; 49 | outline: none; 50 | cursor: pointer; 51 | svg { 52 | fill: ${variables.colorGray}; 53 | :hover { 54 | fill: #0062ff; 55 | } 56 | } 57 | ` 58 | const Title = styled.div` 59 | color: #171725; 60 | font-size: 24px; 61 | margin: 30px 0; 62 | ` 63 | const Delete = styled.button` 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | margin-top: 30px; 68 | background-color: ${variables.colorRed}; 69 | outline: none; 70 | cursor: pointer; 71 | color: ${variables.colorWhite}; 72 | height: 38px; 73 | border-radius: 20px; 74 | border: 1px solid ${variables.colorRed}; 75 | :hover { 76 | color: ${variables.colorRed}; 77 | background-color: ${variables.colorWhite}; 78 | } 79 | ` 80 | 81 | interface ITaskModalProps extends ITaskState { 82 | deleteTask: typeof deleteTask 83 | onClose(): void 84 | } 85 | 86 | const TaskModal: React.FC = props => { 87 | const { type, title, onClose, id, deleteTask } = props 88 | 89 | const element = document.getElementById('modal') 90 | if (!element) { 91 | throw new Error('The element #portal wasn`t found') 92 | } 93 | 94 | const removeTask = (id: string) => { 95 | deleteTask(id) 96 | } 97 | 98 | return ReactDOM.createPortal( 99 | 100 | 101 | 102 | {type} 103 | 104 | 105 | 106 | 107 | 108 | {title} 109 | 110 | 111 | removeTask(id)}>Delete 112 | 113 | , 114 | element 115 | ) 116 | } 117 | 118 | const mapDispatchToProps = { 119 | deleteTask 120 | } 121 | 122 | export default connect( 123 | null, 124 | mapDispatchToProps 125 | )(TaskModal) 126 | -------------------------------------------------------------------------------- /src/components/Common/TaskWrapper/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const variables = { 5 | color: '#0062ff', 6 | colorBorder: '#e2e2ea', 7 | crossSize: 16 8 | } 9 | 10 | const Cross = styled.div` 11 | position: absolute; 12 | left: 50%; 13 | top: 25%; 14 | width: ${variables.crossSize}px; 15 | height: ${variables.crossSize}px; 16 | :before, 17 | :after { 18 | position: absolute; 19 | left: 0; 20 | content: ''; 21 | height: ${variables.crossSize}px; 22 | width: 2px; 23 | background-color: #92929d; 24 | } 25 | :before { 26 | transform: rotate(90deg); 27 | } 28 | :after { 29 | transform: rotate(180deg); 30 | } 31 | ` 32 | const Wrapper = styled.button` 33 | height: 35px; 34 | width: 100%; 35 | border-radius: 0 0 15px 15px; 36 | outline: none; 37 | border: 1px solid ${variables.colorBorder}; 38 | position: relative; 39 | cursor: not-allowed; 40 | background-color: white; 41 | :hover { 42 | border: 1px dashed ${variables.color}; 43 | } 44 | :hover ${Cross}:before { 45 | background-color: ${variables.color}; 46 | } 47 | :hover ${Cross}:after { 48 | background-color: ${variables.color}; 49 | } 50 | ` 51 | 52 | const Button = () => { 53 | return ( 54 | 55 | 56 | 57 | ) 58 | } 59 | 60 | export default Button 61 | -------------------------------------------------------------------------------- /src/components/Common/TaskWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import Task from 'components/Common/Task' 6 | import { ITaskState } from 'store/tasks/types' 7 | import { dragAndDrop } from 'store/tasks/actions' 8 | import { getKanbanOption } from 'store/show/selectors' 9 | import Button from 'components/Common/TaskWrapper/Button' 10 | import IconOval from 'components/Common/Icons/Common/Oval' 11 | 12 | const variables = { 13 | color: '#0062ff', 14 | colorBorder: '#e2e2ea', 15 | crossSize: 16 16 | } 17 | 18 | const Wrapper = styled.div` 19 | width: ${(props: ITaskWrapperProps) => (props.option ? '280px' : 'auto')}; 20 | ` 21 | const Header = styled.div` 22 | border-radius: 15px 15px 0 0; 23 | border-top: 1px solid ${variables.colorBorder}; 24 | border-left: 1px solid ${variables.colorBorder}; 25 | border-right: 1px solid ${variables.colorBorder}; 26 | display: flex; 27 | justify-content: space-between; 28 | ` 29 | const Title = styled.span` 30 | font-size: 16px; 31 | letter-spacing: 0.1px; 32 | color: #696974; 33 | padding: 15px 20px; 34 | ` 35 | const More = styled.div` 36 | padding: 0 20px; 37 | display: flex; 38 | align-items: center; 39 | cursor: not-allowed; 40 | @media (max-width: 450px) { 41 | display: none; 42 | } 43 | ` 44 | const TasksWrapper = styled.div` 45 | height: auto; 46 | border-left: 1px solid ${variables.colorBorder}; 47 | border-right: 1px solid ${variables.colorBorder}; 48 | padding: 20px 0; 49 | background: ${props => 50 | props.dragOver 51 | ? `repeating-linear-gradient( 52 | 45deg, 53 | white, 54 | white 5px, 55 | #E3ECFB 5px, 56 | #E3ECFB 10px 57 | )` 58 | : 'none'}; 59 | ` 60 | 61 | type DragWrapperProps = { 62 | dragOver: boolean 63 | } 64 | 65 | interface ITaskWrapperProps { 66 | dragAndDrop: typeof dragAndDrop 67 | data: ITaskState[] 68 | type: string 69 | option: boolean 70 | } 71 | 72 | const Tasks: Function = (props: ITaskWrapperProps): JSX.Element[] => { 73 | return props.data.map((item: ITaskState) => ( 74 | 75 | )) 76 | } 77 | 78 | const TaskWrapper: React.FC = props => { 79 | const { type, dragAndDrop } = props 80 | 81 | const [dragOver, setDragOver] = React.useState(false) 82 | 83 | const onDragOver = (e: React.DragEvent): void => { 84 | e.preventDefault() 85 | } 86 | 87 | const onDragEnter = (): void => { 88 | setDragOver(prevState => !prevState) 89 | } 90 | 91 | const onDragLeave = (): void => { 92 | setDragOver(prevState => !prevState) 93 | } 94 | 95 | const onDrop = (e: React.DragEvent): void => { 96 | dragAndDrop(e, type) 97 | setDragOver(false) 98 | } 99 | 100 | return ( 101 | 108 | 109 | {type} 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | ) 120 | } 121 | 122 | const mapStateToProps = (state: AppState) => { 123 | return { 124 | option: getKanbanOption(state) 125 | } 126 | } 127 | 128 | const mapDispatchToProps = { 129 | dragAndDrop 130 | } 131 | 132 | export default connect( 133 | mapStateToProps, 134 | mapDispatchToProps 135 | )(TaskWrapper) 136 | -------------------------------------------------------------------------------- /src/components/Common/TeamCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Avatar from 'components/Common/Avatar' 4 | import IconOval from 'components/Common/Icons/Common/Oval' 5 | import { ITeamListUserState } from 'store/teams/types' 6 | 7 | const Wrapper = styled.div` 8 | width: 360px; 9 | min-width: 235px; 10 | height: 104px; 11 | background-color: white; 12 | border-radius: 20px; 13 | padding: 20px; 14 | margin-right: 27px; 15 | @media (max-width: 1600px) { 16 | margin-bottom: 10px; 17 | } 18 | @media (max-width: 800px) { 19 | margin-right: 0; 20 | } 21 | @media (max-width: 450px) { 22 | padding: 10px; 23 | } 24 | ` 25 | const Header = styled.div` 26 | display: grid; 27 | grid-template-columns: 52px auto auto; 28 | column-gap: 20px; 29 | ` 30 | const Image = styled.div` 31 | width: 52px; 32 | height: 52px; 33 | border-radius: 10px; 34 | border: 1px solid #e2e2ea; 35 | display: grid; 36 | justify-items: center; 37 | align-items: center; 38 | img { 39 | width: 45px; 40 | height: 45px; 41 | } 42 | ` 43 | const TeamName = styled.span` 44 | display: grid; 45 | align-items: center; 46 | font-size: 16px; 47 | letter-spacing: 0.1px; 48 | color: #171725; 49 | ` 50 | const More = styled.div` 51 | display: grid; 52 | justify-items: end; 53 | align-items: start; 54 | cursor: not-allowed; 55 | ` 56 | const AvatarWrapper = styled.div` 57 | display: flex; 58 | margin-top: 20px; 59 | img { 60 | margin-right: 10px; 61 | } 62 | div { 63 | margin-right: 10px; 64 | } 65 | ` 66 | 67 | interface ITeamCardProps { 68 | id: number 69 | avatar: string 70 | name: string 71 | users: ITeamListUserState[] 72 | } 73 | 74 | const Avatars = (props: ITeamCardProps): any => { 75 | return props.users.map((avatar: ITeamListUserState, idx: number): object => ( 76 | 77 | )) 78 | } 79 | 80 | const TeamCard: React.FC = props => { 81 | const { avatar, name } = props 82 | 83 | return ( 84 | 85 | 86 | 87 | 88 | 89 | {name} 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ) 99 | } 100 | 101 | export default TeamCard 102 | -------------------------------------------------------------------------------- /src/components/Login/Form/Submit/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import styled from 'styled-components' 4 | import { Redirect } from 'react-router-dom' 5 | import { IUserInfo } from 'store/auth/types' 6 | import { checkAuth } from 'store/auth/actions' 7 | 8 | const Wrapper = styled.div` 9 | display: flex; 10 | position: relative; 11 | ` 12 | const InputLabel = styled.label` 13 | position: absolute; 14 | top: 0; 15 | padding: 0 0 1px 20px; 16 | transition: all 200ms; 17 | opacity: 0.5; 18 | color: #92929d; 19 | ` 20 | const InputText = styled.input` 21 | z-index: 1; 22 | width: 320px; 23 | height: 38px; 24 | border: 1px solid #f1f1f5; 25 | box-sizing: border-box; 26 | border-radius: 15px; 27 | background: #fafafb; 28 | padding: 0 20px; 29 | margin-bottom: 15px; 30 | font-size: 12px; 31 | color: #92929d; 32 | :focus { 33 | outline: none; 34 | ::placeholder { 35 | opacity: 0; 36 | } 37 | } 38 | :focus + ${InputLabel} { 39 | font-size: 75%; 40 | transform: translate3d(0, -100%, 0); 41 | opacity: 1; 42 | color: #fc5a5a; 43 | } 44 | ` 45 | const InputSubmit = styled.input` 46 | width: 320px; 47 | height: 38px; 48 | background: #0062ff; 49 | border-radius: 10px; 50 | border: none; 51 | color: white; 52 | font-size: 12px; 53 | text-align: center; 54 | cursor: pointer; 55 | :focus { 56 | outline: none; 57 | } 58 | :disabled { 59 | opacity: 0.7; 60 | cursor: not-allowed; 61 | } 62 | ` 63 | 64 | interface IFormSubmitProps { 65 | checkAuth: typeof checkAuth 66 | } 67 | 68 | const Sumbit: React.FC = props => { 69 | const { checkAuth } = props 70 | 71 | let dataUser = { 72 | login: '', 73 | password: '' 74 | } 75 | 76 | let dataError = { 77 | login: 'Login error', 78 | password: 'Password error' 79 | } 80 | 81 | const [user, setUser] = React.useState(dataUser) 82 | const [error, setError] = React.useState(dataError) 83 | const [redirect, setRedirect] = React.useState(false) 84 | 85 | const renderRedirect = (): object | void => { 86 | if (redirect) { 87 | return 88 | } 89 | } 90 | 91 | const handleSubmit = (e: React.FormEvent): void => { 92 | e.preventDefault() 93 | if (!error.login && !error.password) { 94 | checkAuth({ 95 | login: user.login, 96 | password: user.password 97 | }) 98 | setRedirect(true) 99 | } 100 | } 101 | 102 | const handleChange = (e: React.ChangeEvent): void => { 103 | const { name, value } = e.target 104 | if (value !== 'admin') { 105 | switch (name) { 106 | case 'login': 107 | setError(prevState => ({ 108 | ...prevState, 109 | login: 'Login error' 110 | })) 111 | break 112 | case 'password': 113 | setError(prevState => ({ 114 | ...prevState, 115 | password: 'Password error' 116 | })) 117 | break 118 | default: 119 | break 120 | } 121 | } else { 122 | name === 'login' 123 | ? setError(prevState => ({ 124 | ...prevState, 125 | login: '' 126 | })) 127 | : setError(prevState => ({ 128 | ...prevState, 129 | password: '' 130 | })) 131 | } 132 | name === 'login' 133 | ? setUser(prevState => ({ 134 | ...prevState, 135 | login: value 136 | })) 137 | : setUser(prevState => ({ 138 | ...prevState, 139 | password: value 140 | })) 141 | } 142 | 143 | return ( 144 | 145 | 146 | 153 | {error.login ? 'Error, your login: admin' : ''} 154 | 155 | 156 | 163 | 164 | {error.password ? 'Error, your password: admin' : ''} 165 | 166 | 167 | 172 | {renderRedirect()} 173 | 174 | ) 175 | } 176 | 177 | const mapDispatchToProps = { 178 | checkAuth 179 | } 180 | 181 | export default connect( 182 | null, 183 | mapDispatchToProps 184 | )(Sumbit) 185 | -------------------------------------------------------------------------------- /src/components/Login/Form/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import LoginSocial from 'components/Login/Social' 4 | import LoginFormSubmit from 'components/Login/Form/Submit' 5 | 6 | const Section = styled.section` 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | width: 360px; 12 | min-width: 320px; 13 | height: 365px; 14 | background-color: white; 15 | border-radius: 20px; 16 | margin: 35px 10px 25px 10px; 17 | ` 18 | 19 | const Form = () => { 20 | return ( 21 | 22 | Login to your account 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | export default Form 30 | -------------------------------------------------------------------------------- /src/components/Login/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { Link } from 'react-router-dom' 4 | 5 | const Wrapper = styled.div` 6 | font-family: 'Roboto', sans-serif; 7 | font-size: 12px; 8 | color: white; 9 | a { 10 | color: white; 11 | text-decoration: none; 12 | :hover { 13 | text-decoration: underline; 14 | } 15 | } 16 | ` 17 | 18 | const Menu = () => { 19 | return ( 20 | 21 | Privacy policy • Terms of use 22 | 23 | ) 24 | } 25 | 26 | export default Menu 27 | -------------------------------------------------------------------------------- /src/components/Login/Social/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import IconGoogle from 'components/Common/Icons/SocialMedia/Google' 4 | import IconFacebook from 'components/Common/Icons/SocialMedia/Facebook' 5 | 6 | const Wrapper = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | ` 11 | const Text = styled.span` 12 | margin: 15px 0; 13 | color: #92929d; 14 | font-size: 12px; 15 | text-transform: uppercase; 16 | ` 17 | const ButtonGoogle = styled.button` 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | width: 320px; 22 | height: 38px; 23 | border: none; 24 | background: #fc5a5a; 25 | border-radius: 10px; 26 | cursor: not-allowed; 27 | :focus { 28 | outline: none; 29 | } 30 | span { 31 | margin-left: 10px; 32 | color: white; 33 | font-size: 12px; 34 | } 35 | ` 36 | const ButtonFacebook = styled(ButtonGoogle)` 37 | background: #50b5ff; 38 | margin-top: 10px; 39 | ` 40 | 41 | const Social = () => { 42 | return ( 43 | 44 | or 45 | 46 | 47 | Continue with Google 48 | 49 | 50 | 51 | Continue with Facebook 52 | 53 | 54 | ) 55 | } 56 | 57 | export default Social 58 | -------------------------------------------------------------------------------- /src/components/Login/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Logo from 'components/Common/Logo' 4 | import LoginForm from 'components/Login/Form' 5 | import LoginMenu from 'components/Login/Menu' 6 | import { createGlobalStyle } from 'styled-components' 7 | 8 | const GlobalStyle = createGlobalStyle` 9 | body { 10 | background-color: #0062ff; 11 | } 12 | ` 13 | const Section = styled.section` 14 | height: 98vh; 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | ` 20 | 21 | const logoProps = { 22 | title: { 23 | size: 26, 24 | color: 'white', 25 | text: 'Square' 26 | }, 27 | image: { 28 | size: 50, 29 | color: 'white' 30 | } 31 | } 32 | 33 | const Login = () => { 34 | return ( 35 | <> 36 | 37 | 38 | 39 | 40 | 41 | 42 | > 43 | ) 44 | } 45 | 46 | export default Login 47 | -------------------------------------------------------------------------------- /src/components/Main/Content/Tasks/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import Task from 'components/Common/Task' 6 | import { ITaskState } from 'store/tasks/types' 7 | import { getTasks } from 'store/tasks/selectors' 8 | import IconOval from 'components/Common/Icons/Common/Oval' 9 | 10 | const Wrapper = styled.div` 11 | border: 1px solid #e2e2ea; 12 | border-radius: 23px; 13 | margin: 35px 0 20px 0; 14 | display: flex; 15 | flex-direction: column; 16 | padding: 5px; 17 | ` 18 | const Header = styled.div` 19 | display: flex; 20 | justify-content: space-between; 21 | ` 22 | const TeamsTitle = styled.span` 23 | font-size: 16px; 24 | letter-spacing: 0.1px; 25 | color: #696974; 26 | padding: 15px 20px; 27 | ` 28 | const TeamsMore = styled.div` 29 | padding: 0 20px; 30 | display: flex; 31 | align-items: center; 32 | cursor: not-allowed; 33 | @media (max-width: 450px) { 34 | display: none; 35 | } 36 | ` 37 | const Teams = styled.div` 38 | display: flex; 39 | flex-wrap: wrap; 40 | ` 41 | const TasksWrapper = styled.div` 42 | display: flex; 43 | flex-wrap: wrap; 44 | ` 45 | 46 | interface IContentTasksProps { 47 | tasks: ITaskState[] 48 | } 49 | 50 | const Tasks: React.FC = props => { 51 | const { tasks } = props 52 | 53 | const tasksList = tasks.map((item: ITaskState) => ( 54 | 55 | )) 56 | 57 | return ( 58 | 59 | 60 | Tasks 61 | 62 | 63 | 64 | 65 | 66 | {tasksList} 67 | 68 | 69 | ) 70 | } 71 | 72 | const mapStateToProps = (state: AppState) => { 73 | return { 74 | tasks: getTasks(state) 75 | } 76 | } 77 | 78 | export default connect(mapStateToProps)(Tasks) 79 | -------------------------------------------------------------------------------- /src/components/Main/Content/Teams/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import Loader from 'components/Common/Loader' 6 | import Big from 'components/Common/Buttons/Big' 7 | import { getTeams } from 'store/teams/selectors' 8 | import { fetchTeams } from 'store/teams/actions' 9 | import TeamCard from 'components/Common/TeamCard' 10 | import { ITeamListState } from 'store/teams/types' 11 | import IconOval from 'components/Common/Icons/Common/Oval' 12 | 13 | const Wrapper = styled.div` 14 | border: 1px solid #e2e2ea; 15 | border-radius: 23px; 16 | margin: 35px 0 20px 0; 17 | display: flex; 18 | flex-direction: column; 19 | padding: 5px; 20 | ` 21 | const Header = styled.div` 22 | display: flex; 23 | justify-content: space-between; 24 | ` 25 | const TeamsTitle = styled.span` 26 | font-size: 16px; 27 | letter-spacing: 0.1px; 28 | color: #696974; 29 | padding: 15px 20px; 30 | ` 31 | const TeamsMore = styled.div` 32 | padding: 0 20px; 33 | display: flex; 34 | align-items: center; 35 | cursor: not-allowed; 36 | @media (max-width: 450px) { 37 | display: none; 38 | } 39 | ` 40 | const TeamsList = styled.div` 41 | display: flex; 42 | flex-wrap: wrap; 43 | ` 44 | 45 | interface IContentTeamsProps { 46 | fetchTeams: () => void 47 | teams: ITeamListState[] 48 | } 49 | 50 | const TeamCards: Function = (props: IContentTeamsProps): JSX.Element[] => { 51 | return props.teams.map((card: ITeamListState) => ( 52 | 53 | )) 54 | } 55 | 56 | const Teams: React.FC = props => { 57 | const { teams, fetchTeams } = props 58 | 59 | React.useEffect(() => { 60 | !teams.length && fetchTeams() 61 | }, []) 62 | 63 | return ( 64 | 65 | 66 | Teams 67 | 68 | 69 | 70 | 71 | 72 | {teams.length ? : } 73 | 74 | 75 | 76 | ) 77 | } 78 | 79 | const mapStateToProps = (state: AppState) => { 80 | return { 81 | teams: getTeams(state) 82 | } 83 | } 84 | 85 | const mapDispatchToProps = { 86 | fetchTeams 87 | } 88 | 89 | export default connect( 90 | mapStateToProps, 91 | mapDispatchToProps 92 | )(Teams) 93 | -------------------------------------------------------------------------------- /src/components/Main/Content/Title/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const HeaderContent = styled.div` 5 | display: flex; 6 | align-items: center; 7 | height: 38px; 8 | ` 9 | const Username = styled.span` 10 | font-size: 24px; 11 | text-align: center; 12 | letter-spacing: 0.1px; 13 | color: #171725; 14 | @media (max-width: 450px) { 15 | display: none; 16 | } 17 | ` 18 | const WelcomeText = styled.span` 19 | font-size: 18px; 20 | letter-spacing: 0.1px; 21 | color: #92929d; 22 | margin-left: 10px; 23 | font-family: 'Roboto', sans-serif; 24 | ` 25 | 26 | const Title = () => { 27 | return ( 28 | 29 | 30 | Hi James, 31 | here’s your currently projects 32 | 33 | 34 | ) 35 | } 36 | 37 | export default Title 38 | -------------------------------------------------------------------------------- /src/components/Main/Content/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import ErrorBoundary from 'components/Common/ErrorBoundary' 4 | import ContentTitle from 'components/Main/Content/Title' 5 | import ContentTeams from 'components/Main/Content/Teams' 6 | import ContentTasks from 'components/Main/Content/Tasks' 7 | 8 | const Wrapper = styled.section` 9 | display: flex; 10 | flex-direction: column; 11 | width: 100%; 12 | min-width: 250px; 13 | background-color: #fafafa; 14 | padding: 40px; 15 | @media (max-width: 450px) { 16 | padding: 10px; 17 | } 18 | ` 19 | 20 | const Content = () => { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export default Content 33 | -------------------------------------------------------------------------------- /src/components/Main/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Loader from 'components/Common/Loader' 4 | import Header from 'components/Common/Header' 5 | import Sidebar from 'components/Common/Sidebar' 6 | import ErrorBoundary from 'components/Common/ErrorBoundary' 7 | 8 | const Content = React.lazy(() => import('components/Main/Content')) 9 | 10 | const Wrapper = styled.div` 11 | display: flex; 12 | max-width: 1600px; 13 | margin: 0 auto; 14 | ` 15 | 16 | const Main = () => { 17 | return ( 18 | <> 19 | 20 | 21 | 22 | 23 | }> 24 | 25 | 26 | 27 | 28 | > 29 | ) 30 | } 31 | 32 | export default Main 33 | -------------------------------------------------------------------------------- /src/components/Messages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Header from 'components/Common/Header' 4 | import Sidebar from 'components/Common/Sidebar' 5 | 6 | const MainWrapper = styled.div` 7 | display: flex; 8 | max-width: 1600px; 9 | margin: 0 auto; 10 | ` 11 | const Wrapper = styled.section` 12 | display: flex; 13 | flex-direction: column; 14 | width: 100%; 15 | min-width: 250px; 16 | background-color: #fafafa; 17 | padding: 40px; 18 | @media (max-width: 450px) { 19 | padding: 10px; 20 | } 21 | ` 22 | const Username = styled.span` 23 | font-size: 24px; 24 | text-align: center; 25 | letter-spacing: 0.1px; 26 | color: #171725; 27 | @media (max-width: 450px) { 28 | display: none; 29 | } 30 | ` 31 | const Text = styled.span` 32 | font-size: 18px; 33 | letter-spacing: 0.1px; 34 | color: #92929d; 35 | margin-left: 10px; 36 | font-family: 'Roboto', sans-serif; 37 | ` 38 | const HeaderContent = styled.div` 39 | display: flex; 40 | align-items: center; 41 | height: 38px; 42 | ` 43 | 44 | const Messages = () => { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | Hi James, 54 | here’s your currently messages 55 | 56 | 57 | 58 | 59 | > 60 | ) 61 | } 62 | 63 | export default Messages 64 | -------------------------------------------------------------------------------- /src/components/Schedule/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Header from 'components/Common/Header' 4 | import Sidebar from 'components/Common/Sidebar' 5 | 6 | const MainWrapper = styled.div` 7 | display: flex; 8 | max-width: 1600px; 9 | margin: 0 auto; 10 | ` 11 | const Wrapper = styled.section` 12 | display: flex; 13 | flex-direction: column; 14 | width: 100%; 15 | min-width: 250px; 16 | background-color: #fafafa; 17 | padding: 40px; 18 | @media (max-width: 450px) { 19 | padding: 10px; 20 | } 21 | ` 22 | const Username = styled.span` 23 | font-size: 24px; 24 | text-align: center; 25 | letter-spacing: 0.1px; 26 | color: #171725; 27 | @media (max-width: 450px) { 28 | display: none; 29 | } 30 | ` 31 | const Text = styled.span` 32 | font-size: 18px; 33 | letter-spacing: 0.1px; 34 | color: #92929d; 35 | margin-left: 10px; 36 | font-family: 'Roboto', sans-serif; 37 | ` 38 | const HeaderContent = styled.div` 39 | display: flex; 40 | align-items: center; 41 | height: 38px; 42 | ` 43 | 44 | const Schedule = () => { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | Hi James, 54 | here’s your currently schedule 55 | 56 | 57 | 58 | 59 | > 60 | ) 61 | } 62 | 63 | export default Schedule 64 | -------------------------------------------------------------------------------- /src/components/Settings/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Header from 'components/Common/Header' 4 | import Sidebar from 'components/Common/Sidebar' 5 | 6 | const MainWrapper = styled.div` 7 | display: flex; 8 | max-width: 1600px; 9 | margin: 0 auto; 10 | ` 11 | const Wrapper = styled.section` 12 | display: flex; 13 | flex-direction: column; 14 | width: 100%; 15 | min-width: 250px; 16 | background-color: #fafafa; 17 | padding: 40px; 18 | @media (max-width: 450px) { 19 | padding: 10px; 20 | } 21 | ` 22 | const Username = styled.span` 23 | font-size: 24px; 24 | text-align: center; 25 | letter-spacing: 0.1px; 26 | color: #171725; 27 | @media (max-width: 450px) { 28 | display: none; 29 | } 30 | ` 31 | const Text = styled.span` 32 | font-size: 18px; 33 | letter-spacing: 0.1px; 34 | color: #92929d; 35 | margin-left: 10px; 36 | font-family: 'Roboto', sans-serif; 37 | ` 38 | const HeaderContent = styled.div` 39 | display: flex; 40 | align-items: center; 41 | height: 38px; 42 | ` 43 | 44 | const Settings = () => { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | Hi James, 54 | here’s your currently settings 55 | 56 | 57 | 58 | 59 | > 60 | ) 61 | } 62 | 63 | export default Settings 64 | -------------------------------------------------------------------------------- /src/components/Tasks/Content/Title/Selector/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import { showMore } from 'store/show/actions' 6 | import { IShowTypes } from 'store/show/types' 7 | import Checkbox from 'components/Common/Checkbox' 8 | import { getShowState } from 'store/show/selectors' 9 | 10 | const Sort = styled.div` 11 | display: flex; 12 | justify-content: space-between; 13 | position: relative; 14 | width: 200px; 15 | height: 38px; 16 | box-shadow: 0 0 7px rgba(41, 41, 50, 0.1); 17 | border-radius: 8px; 18 | background-color: white; 19 | cursor: pointer; 20 | ` 21 | const Select = styled.div` 22 | position: absolute; 23 | width: 199px; 24 | height: 110px; 25 | left: 0; 26 | top: 39px; 27 | background: white; 28 | border: 1px solid #f1f1f5; 29 | box-shadow: 0 5px 15px rgba(68, 68, 79, 0.1); 30 | border-radius: 8px; 31 | display: flex; 32 | flex-direction: column; 33 | z-index: 1; 34 | ` 35 | const ArrowWrapper = styled.div` 36 | height: 100%; 37 | width: 30px; 38 | border-left: 1px solid #f1f1f5; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | ` 43 | const Arrow = styled.div` 44 | height: 0; 45 | width: 0; 46 | border: solid #92929d; 47 | border-width: 0 2px 2px 0; 48 | display: flex; 49 | padding: 3px; 50 | ` 51 | const ArrowDown = styled(Arrow)` 52 | transform: rotate(45deg); 53 | ` 54 | const ArrowUp = styled(Arrow)` 55 | transform: rotate(135deg); 56 | ` 57 | const ShowWrapper = styled.div` 58 | display: flex; 59 | align-items: center; 60 | padding: 0 0 0 20px; 61 | ` 62 | const Show = styled.span` 63 | font-size: 14px; 64 | letter-spacing: 0.5px; 65 | color: #696974; 66 | ` 67 | const What = styled.span` 68 | font-size: 14px; 69 | letter-spacing: 0.1px; 70 | color: #44444f; 71 | margin-left: 10px; 72 | ` 73 | 74 | interface IContentTitleProps { 75 | showMore: typeof showMore 76 | showState: IShowTypes 77 | } 78 | 79 | const Selector: React.FC = props => { 80 | const { showState, showMore } = props 81 | 82 | const [opened, setOpened] = React.useState(false) 83 | const [filter, setFilter] = React.useState(['All tasks']) 84 | 85 | const handleOpened = (e: React.ChangeEvent): void => { 86 | if (e.target === e.currentTarget) { 87 | setOpened(prevState => !prevState) 88 | } 89 | } 90 | 91 | const handleOpenedSimple = (): void => { 92 | setOpened(prevState => !prevState) 93 | } 94 | 95 | const handleCheckbox = (e: React.ChangeEvent): void => { 96 | const name = e.target.name 97 | setFilter([e.target.value]) 98 | showMore({ ...showState, [name]: !showState[name] }) 99 | } 100 | 101 | return ( 102 | 103 | 104 | Show: 105 | {filter[filter.length - 1]} 106 | 107 | 108 | {opened ? : } 109 | 110 | {opened && ( 111 | 112 | 118 | 124 | 130 | 131 | )} 132 | 133 | ) 134 | } 135 | 136 | const mapStateToProps = (state: AppState) => { 137 | return { showState: getShowState(state) } 138 | } 139 | 140 | const mapDispatchToProps = { 141 | showMore 142 | } 143 | 144 | export default connect( 145 | mapStateToProps, 146 | mapDispatchToProps 147 | )(Selector) 148 | -------------------------------------------------------------------------------- /src/components/Tasks/Content/Title/Switcher/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import styled from 'styled-components' 4 | import { switchKanban } from 'store/show/actions' 5 | import GanttIcon from 'components/Common/Icons/Switcher/Gantt' 6 | import KanbanIcon from 'components/Common/Icons/Switcher/Kanban' 7 | import DefaultIcon from 'components/Common/Icons/Switcher/Default' 8 | 9 | const Wrapper = styled.div` 10 | display: flex; 11 | margin-left: 20px; 12 | @media (max-width: 450px) { 13 | display: none; 14 | } 15 | ` 16 | const Icon = styled.div` 17 | width: 38px; 18 | height: 38px; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | background-color: #fff; 23 | cursor: pointer; 24 | svg { 25 | fill: #b5b5be; 26 | } 27 | ` 28 | const Kanban = styled(Icon)` 29 | border-radius: 10px 0 0 10px; 30 | background-color: ${(props: SwitcherProps) => (props.type ? '#b5b5be' : '#fff')}; 31 | svg { 32 | fill: ${(props: SwitcherProps) => (props.type ? '#fff' : '#b5b5be')}; 33 | } 34 | ` 35 | const Default = styled(Icon)` 36 | border-right: 1px solid #f1f1f5; 37 | border-left: 1px solid #f1f1f5; 38 | background-color: ${(props: SwitcherProps) => (props.type ? '#b5b5be' : '#fff')}; 39 | svg { 40 | fill: ${(props: SwitcherProps) => (props.type ? '#fff' : '#b5b5be')}; 41 | } 42 | ` 43 | const Gantt = styled(Icon)` 44 | border-radius: 0 10px 10px 0; 45 | cursor: not-allowed; 46 | ` 47 | 48 | type SwitcherProps = { 49 | type: boolean | number 50 | } 51 | 52 | interface IContentTitleSwitcherProps { 53 | switcher: typeof switchKanban 54 | } 55 | 56 | const Switcher: React.FC = props => { 57 | const { switcher } = props 58 | 59 | const [typeKanban, setTypeKanban] = React.useState(true) 60 | const [typeDefault, setTypeDefault] = React.useState(false) 61 | 62 | const handleTypeKanban = () => { 63 | switcher(true) 64 | setTypeKanban(true) 65 | setTypeDefault(false) 66 | } 67 | 68 | const handleTypeDefault = () => { 69 | switcher(false) 70 | setTypeDefault(true) 71 | setTypeKanban(false) 72 | } 73 | 74 | return ( 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ) 87 | } 88 | 89 | const mapDispatchToProps = { 90 | switcher: switchKanban 91 | } 92 | 93 | export default connect( 94 | null, 95 | mapDispatchToProps 96 | )(Switcher) 97 | -------------------------------------------------------------------------------- /src/components/Tasks/Content/Title/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import ContentTitleSelector from 'components/Tasks/Content/Title/Selector' 4 | import ContentTitleSwitcher from 'components/Tasks/Content/Title/Switcher' 5 | 6 | const Wrapper = styled.div` 7 | display: flex; 8 | align-items: center; 9 | justify-content: space-between; 10 | @media (max-width: 450px) { 11 | justify-content: center; 12 | } 13 | ` 14 | const Header = styled.div` 15 | @media (max-width: 1050px) { 16 | display: none; 17 | } 18 | ` 19 | const Username = styled.span` 20 | font-size: 24px; 21 | text-align: center; 22 | letter-spacing: 0.1px; 23 | color: #171725; 24 | @media (max-width: 450px) { 25 | display: none; 26 | } 27 | ` 28 | const Text = styled.span` 29 | font-size: 18px; 30 | letter-spacing: 0.1px; 31 | color: #92929d; 32 | margin-left: 10px; 33 | font-family: 'Roboto', sans-serif; 34 | ` 35 | const Controls = styled.div` 36 | display: flex; 37 | margin-right: 20px; 38 | ` 39 | 40 | const Title = () => { 41 | return ( 42 | 43 | 44 | Hi James, 45 | here’s your currently tasks 46 | 47 | 48 | 49 | 50 | 51 | 52 | ) 53 | } 54 | 55 | export default Title 56 | -------------------------------------------------------------------------------- /src/components/Tasks/Content/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { AppState } from 'store' 3 | import { connect } from 'react-redux' 4 | import styled from 'styled-components' 5 | import Loader from 'components/Common/Loader' 6 | import { IShowTypes } from 'store/show/types' 7 | import { ITaskState } from 'store/tasks/types' 8 | import { fetchTasks } from 'store/tasks/actions' 9 | import TaskWrapper from 'components/Common/TaskWrapper' 10 | import ContentTitle from 'components/Tasks/Content/Title' 11 | import { filteredTasks, getTasks } from 'store/tasks/selectors' 12 | import { getShowState, getKanbanOption } from 'store/show/selectors' 13 | 14 | const Wrapper = styled.div` 15 | display: flex; 16 | flex-direction: column; 17 | width: 100%; 18 | min-width: 250px; 19 | background-color: #fafafa; 20 | padding: 40px; 21 | @media (max-width: 450px) { 22 | padding: 10px; 23 | } 24 | ` 25 | const Tasks = styled.div` 26 | margin-top: 35px; 27 | display: grid; 28 | grid-template-columns: ${(props: IContentProps) => 29 | props.option ? `repeat(auto-fill, minmax(280px, 1fr))` : 'none'}; 30 | grid-template-rows: ${(props: IContentProps) => 31 | props.option ? 'none' : 'repeat(4, auto)'}; 32 | grid-column-gap: 20px; 33 | grid-row-gap: 20px; 34 | ` 35 | 36 | interface IContentProps { 37 | backlog: ITaskState[] 38 | progress: ITaskState[] 39 | complete: ITaskState[] 40 | showAll: boolean 41 | showBacklog: boolean 42 | showState: IShowTypes 43 | option: boolean 44 | fetchTasks: typeof fetchTasks 45 | tasks: ITaskState[] 46 | } 47 | 48 | const types = { 49 | all: 'All tasks', 50 | backlog: 'Backlog', 51 | progress: 'In Progress', 52 | complete: 'Complete' 53 | } 54 | 55 | const Content: React.FC = props => { 56 | const { tasks, showState, backlog, progress, complete, fetchTasks } = props 57 | 58 | React.useEffect(() => { 59 | !tasks.length && fetchTasks() 60 | }, []) 61 | 62 | return ( 63 | 64 | 65 | {tasks.length ? ( 66 | 67 | {showState.backlog && } 68 | {showState.progress && ( 69 | 70 | )} 71 | {showState.complete && ( 72 | 73 | )} 74 | 75 | 76 | ) : ( 77 | 78 | )} 79 | 80 | ) 81 | } 82 | 83 | const mapStateToProps = (state: AppState) => { 84 | return { 85 | tasks: getTasks(state), 86 | option: getKanbanOption(state), 87 | showState: getShowState(state), 88 | backlog: filteredTasks(state, types.backlog), 89 | progress: filteredTasks(state, types.progress), 90 | complete: filteredTasks(state, types.complete) 91 | } 92 | } 93 | 94 | const mapDispatchToProps = { 95 | fetchTasks 96 | } 97 | 98 | export default connect( 99 | mapStateToProps, 100 | mapDispatchToProps 101 | )(Content) 102 | -------------------------------------------------------------------------------- /src/components/Tasks/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Header from 'components/Common/Header' 4 | import Sidebar from 'components/Common/Sidebar' 5 | import ErrorBoundary from 'components/Common/ErrorBoundary' 6 | 7 | const Content = React.lazy(() => import('components/Tasks/Content')) 8 | 9 | const Wrapper = styled.div` 10 | display: flex; 11 | max-width: 1600px; 12 | margin: 0 auto; 13 | ` 14 | 15 | const Tasks = () => { 16 | return ( 17 | <> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | > 28 | ) 29 | } 30 | 31 | export default Tasks 32 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Square Dashboard: React SPA 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from 'components/App' 4 | import configureStore from 'store' 5 | import { Provider } from 'react-redux' 6 | import { Normalize } from 'styled-normalize' 7 | 8 | const store = configureStore() 9 | 10 | ReactDOM.render( 11 | 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ) 17 | -------------------------------------------------------------------------------- /src/store/auth/actions.ts: -------------------------------------------------------------------------------- 1 | import { AUTH, IAuthCheckAction, IUserInfo } from 'store/auth/types' 2 | 3 | export const checkAuth = (userInfo: IUserInfo): IAuthCheckAction => { 4 | return { 5 | type: AUTH, 6 | payload: userInfo 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/store/auth/reducers.ts: -------------------------------------------------------------------------------- 1 | import { AUTH, IAuthState, IAuthCheckAction } from 'store/auth/types' 2 | 3 | const initialState: IAuthState = { 4 | isAuth: false, 5 | login: '', 6 | password: '' 7 | } 8 | 9 | export function auth( 10 | state = initialState, 11 | action: IAuthCheckAction 12 | ): IAuthState { 13 | switch (action.type) { 14 | case AUTH: 15 | return Object.assign( 16 | {}, 17 | { 18 | isAuth: true, 19 | login: action.payload.login, 20 | password: action.payload.password 21 | } 22 | ) 23 | default: 24 | return state 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/store/auth/selectors.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heysafronov/square-react-dashboard/f12759a0980299583134e6e7b9ba5d34521d47d5/src/store/auth/selectors.ts -------------------------------------------------------------------------------- /src/store/auth/types.ts: -------------------------------------------------------------------------------- 1 | export const AUTH = 'AUTH' 2 | 3 | export interface IUserInfo { 4 | login: string 5 | password: string 6 | } 7 | 8 | export interface IAuthState { 9 | isAuth: boolean 10 | login: string 11 | password: string 12 | } 13 | 14 | export interface IAuthCheckAction { 15 | type: typeof AUTH 16 | payload: { 17 | login: string 18 | password: string 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import thunkMiddleware from 'redux-thunk' 2 | import { auth } from 'store/auth/reducers' 3 | import { show } from 'store/show/reducers' 4 | import { tasks } from 'store/tasks/reducers' 5 | import { teams } from 'store/teams/reducers' 6 | import { createStore, combineReducers, applyMiddleware } from 'redux' 7 | import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly' 8 | 9 | const rootReducer = combineReducers({ 10 | auth, 11 | show, 12 | tasks, 13 | teams 14 | }) 15 | 16 | export type AppState = ReturnType 17 | 18 | export default function configureStore() { 19 | const middleware = [thunkMiddleware] 20 | const middleWareEnhancer = applyMiddleware(...middleware) 21 | return createStore(rootReducer, composeWithDevTools(middleWareEnhancer)) 22 | } 23 | -------------------------------------------------------------------------------- /src/store/show/actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SHOW_MORE, 3 | SWITCH_KANBAN, 4 | IShowMoreAction, 5 | IShowTypes, 6 | ISwitchKanbanAction 7 | } from 'store/show/types' 8 | 9 | export const showMore = (types: IShowTypes): IShowMoreAction => { 10 | return { 11 | type: SHOW_MORE, 12 | payload: types 13 | } 14 | } 15 | 16 | export const switchKanban = (option: boolean): ISwitchKanbanAction => { 17 | return { 18 | type: SWITCH_KANBAN, 19 | payload: option 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/store/show/reducers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SHOW_MORE, 3 | IShowState, 4 | IShowMoreAction, 5 | SWITCH_KANBAN, 6 | ISwitchKanbanAction 7 | } from 'store/show/types' 8 | 9 | const initialState: IShowState = { 10 | list: { 11 | backlog: true, 12 | progress: true, 13 | complete: true 14 | }, 15 | kanban: true 16 | } 17 | 18 | export function show( 19 | state = initialState, 20 | action: IShowMoreAction | ISwitchKanbanAction 21 | ): IShowState { 22 | switch (action.type) { 23 | case SHOW_MORE: 24 | return { ...state, list: { ...action.payload } } 25 | case SWITCH_KANBAN: 26 | return { ...state, kanban: action.payload } 27 | default: 28 | return state 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/store/show/selectors.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from 'store' 2 | import { createSelector } from 'reselect' 3 | import { IShowTypes } from 'store/show/types' 4 | 5 | const showState = (state: AppState): IShowTypes => state.show.list 6 | const kanbanOption = (state: AppState): boolean => state.show.kanban 7 | 8 | export const getShowState = createSelector( 9 | [showState], 10 | list => { 11 | return list 12 | } 13 | ) 14 | 15 | export const getKanbanOption = createSelector( 16 | [kanbanOption], 17 | option => { 18 | return option 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /src/store/show/types.ts: -------------------------------------------------------------------------------- 1 | export const SHOW_MORE = 'SHOW_MORE' 2 | export const SWITCH_KANBAN = 'SWITCH_KANBAN' 3 | 4 | export interface IShowTypes { 5 | backlog: boolean 6 | progress: boolean 7 | complete: boolean 8 | } 9 | 10 | export interface IShowState { 11 | list: IShowTypes 12 | kanban: boolean 13 | } 14 | 15 | export interface IShowMoreAction { 16 | type: typeof SHOW_MORE 17 | payload: IShowTypes 18 | } 19 | 20 | export interface ISwitchKanbanAction { 21 | type: typeof SWITCH_KANBAN 22 | payload: boolean 23 | } 24 | -------------------------------------------------------------------------------- /src/store/tasks/actions.ts: -------------------------------------------------------------------------------- 1 | import instance from 'utils/axios' 2 | import { Dispatch } from 'redux' 3 | import { 4 | FETCH_TASKS, 5 | DELETE_TASK, 6 | DRAG_AND_DROP, 7 | ITasksDragAndDropAction, 8 | ITasksDeleteTasksAction 9 | } from 'store/tasks/types' 10 | 11 | export const fetchTasks = () => async (dispatch: Dispatch): Promise => { 12 | try { 13 | const { data } = await instance.get('tasks.json') 14 | dispatch({ type: FETCH_TASKS, payload: data }) 15 | } catch (err) { 16 | console.error(`[Action: fetchTasks] - ${err}`) 17 | } 18 | } 19 | 20 | export const dragAndDrop = ( 21 | e: object, 22 | type: string 23 | ): ITasksDragAndDropAction => { 24 | return { 25 | type: DRAG_AND_DROP, 26 | payload: { e, type } 27 | } 28 | } 29 | 30 | export const deleteTask = (id: string): ITasksDeleteTasksAction => { 31 | return { 32 | type: DELETE_TASK, 33 | payload: id 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/store/tasks/reducers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DRAG_AND_DROP, 3 | FETCH_TASKS, 4 | DELETE_TASK, 5 | ITaskState, 6 | ITasksDragAndDropAction, 7 | ITasksFetchTasksAction, 8 | ITasksDeleteTasksAction 9 | } from 'store/tasks/types' 10 | 11 | const checkChrome = (id: string): string => { 12 | return id[0] === '<' ? id.replace(/[^\d]/g, '').slice(1) : id 13 | } 14 | 15 | type Actions = 16 | | ITasksDragAndDropAction 17 | | ITasksFetchTasksAction 18 | | ITasksDeleteTasksAction 19 | 20 | const initialState: ITaskState[] = [] 21 | 22 | export function tasks(state = initialState, action: Actions): ITaskState[] { 23 | const { type, payload } = action 24 | switch (type) { 25 | case DRAG_AND_DROP: 26 | const id = payload.e.dataTransfer.getData('text/html') 27 | const checkedId = checkChrome(id) 28 | return state.filter(task => { 29 | if (task.id === checkedId) { 30 | task.type = payload.type 31 | } 32 | return task 33 | }) 34 | case FETCH_TASKS: 35 | return [...state, ...action.payload] 36 | case DELETE_TASK: 37 | return state.filter(task => task.id !== payload) 38 | default: 39 | return state 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/store/tasks/selectors.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from 'store' 2 | import { createSelector } from 'reselect' 3 | import { ITaskState } from 'store/tasks/types' 4 | 5 | const tasks = (state: AppState): ITaskState[] => state.tasks 6 | 7 | export const getTasks = createSelector( 8 | [tasks], 9 | list => { 10 | return list 11 | } 12 | ) 13 | 14 | export const filteredTasks = (state: any, type: string): ITaskState[] => { 15 | return state.tasks.filter((task: ITaskState) => { 16 | return task.type === type 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/store/tasks/types.ts: -------------------------------------------------------------------------------- 1 | import { ITeamListUserState } from 'store/teams/types' 2 | 3 | export const DELETE_TASK = 'DELETE_TASK' 4 | export const FETCH_TASKS = 'FETCH_TASKS' 5 | export const DRAG_AND_DROP = 'DRAG_AND_DROP' 6 | 7 | export interface ITaskState { 8 | id: string 9 | title: string 10 | team: string 11 | attach: number 12 | status: string 13 | score: { days: number; colors: { bg: string; text: string } } 14 | line: number 15 | type: string 16 | users: ITeamListUserState[] 17 | } 18 | 19 | export interface ITasksDragAndDropAction { 20 | type: typeof DRAG_AND_DROP 21 | payload: any 22 | } 23 | 24 | export interface ITasksFetchTasksAction { 25 | type: typeof FETCH_TASKS 26 | payload: ITaskState[] 27 | } 28 | 29 | export interface ITasksDeleteTasksAction { 30 | type: typeof DELETE_TASK 31 | payload: string 32 | } 33 | -------------------------------------------------------------------------------- /src/store/teams/actions.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux' 2 | import instance from 'utils/axios' 3 | import { FETCH_TEAMS } from 'store/teams/types' 4 | 5 | export const fetchTeams = () => async (dispatch: Dispatch): Promise => { 6 | try { 7 | const { data } = await instance.get('teams.json') 8 | dispatch({ type: FETCH_TEAMS, payload: data }) 9 | } catch (err) { 10 | console.error(`[Action: fetchTeams] - ${err}`) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/store/teams/reducers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FETCH_TEAMS, 3 | ITeamsState, 4 | ITeamsFetchTeamsAction 5 | } from 'store/teams/types' 6 | 7 | const initialState: ITeamsState = { 8 | list: [] 9 | } 10 | 11 | export function teams( 12 | state = initialState, 13 | action: ITeamsFetchTeamsAction 14 | ): ITeamsState { 15 | switch (action.type) { 16 | case FETCH_TEAMS: 17 | return { ...state, list: action.payload } 18 | default: 19 | return state 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/store/teams/selectors.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from 'store' 2 | import { createSelector } from 'reselect' 3 | import { ITeamListState } from 'store/teams/types' 4 | 5 | const teams = (state: AppState): ITeamListState[] => state.teams.list 6 | 7 | export const getTeams = createSelector( 8 | [teams], 9 | list => { 10 | return list 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /src/store/teams/types.ts: -------------------------------------------------------------------------------- 1 | export const FETCH_TEAMS = 'FETCH_TEAMS' 2 | 3 | export interface ITeamListUserState { 4 | size: number 5 | name: string 6 | color: string 7 | avatar: string 8 | } 9 | 10 | export interface ITeamListState { 11 | id: number 12 | avatar: string 13 | name: string 14 | users: ITeamListUserState[] 15 | } 16 | 17 | export interface ITeamsState { 18 | list: ITeamListState[] | object 19 | } 20 | 21 | export interface ITeamsFetchTeamsAction { 22 | type: typeof FETCH_TEAMS 23 | payload: ITeamsState 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const instance = axios.create({ 4 | baseURL: process.env.API_URL 5 | }) 6 | 7 | export default instance 8 | -------------------------------------------------------------------------------- /src/utils/idGenerator.ts: -------------------------------------------------------------------------------- 1 | export const randomId = (): string => { 2 | return Math.round(Math.random() * 36 ** 8).toString(36) 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "module": "commonjs", 5 | "target": "ES5", 6 | "baseUrl": "./src/", 7 | "outDir": "./dist/", 8 | "moduleResolution": "node", 9 | "lib": [ 10 | "es2015", 11 | "dom.iterable", 12 | "es2016.array.include", 13 | "es2017.object", 14 | "dom" 15 | ], 16 | "allowJs": true, 17 | "noEmit": true, 18 | "strict": true, 19 | "importHelpers": true, 20 | "skipLibCheck": true, 21 | "esModuleInterop": true, 22 | "strictNullChecks": true, 23 | "removeComments": true, 24 | "alwaysStrict": true, 25 | "keyofStringsOnly": true, 26 | "allowUnreachableCode": false, 27 | "noImplicitAny": true, 28 | "noImplicitThis": true, 29 | "noUnusedLocals": true, 30 | "noUnusedParameters": true, 31 | "noImplicitReturns": true, 32 | "noFallthroughCasesInSwitch": true, 33 | "forceConsistentCasingInFileNames": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const Dotenv = require('dotenv-webpack') 3 | const HTMLWebpack = require('html-webpack-plugin') 4 | const TerserWebpack = require('terser-webpack-plugin') 5 | const BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 6 | 7 | const isDevelopment = process.env.NODE_ENV === 'development' 8 | 9 | const optimization = () => { 10 | const development = { 11 | splitChunks: { 12 | chunks: 'all' 13 | } 14 | } 15 | const production = { 16 | minimize: true, 17 | minimizer: [new TerserWebpack({ cache: true })], 18 | splitChunks: { 19 | minSize: 10000, 20 | maxSize: 250000 21 | } 22 | } 23 | return isDevelopment ? development : production 24 | } 25 | 26 | const fileName = ext => 27 | isDevelopment ? `[name].${ext}` : `[name].[hash].${ext}` 28 | 29 | const devTool = () => (isDevelopment ? 'source-map' : '') 30 | 31 | module.exports = { 32 | mode: 'production', 33 | entry: './src/index', 34 | output: { 35 | path: path.resolve(__dirname, 'dist'), 36 | filename: fileName('js'), 37 | publicPath: './' 38 | }, 39 | resolve: { 40 | extensions: ['.ts', '.tsx', '.js', '.jsx'], 41 | alias: { 42 | src: path.resolve(__dirname, 'src/'), 43 | api: path.resolve(__dirname, 'src/api/'), 44 | store: path.resolve(__dirname, 'src/store/'), 45 | utils: path.resolve(__dirname, 'src/utils/'), 46 | assets: path.resolve(__dirname, 'src/assets/'), 47 | components: path.resolve(__dirname, 'src/components/') 48 | } 49 | }, 50 | devtool: devTool(), 51 | watchOptions: { 52 | aggregateTimeout: 1000, 53 | poll: 1000 54 | }, 55 | performance: { 56 | hints: false 57 | }, 58 | optimization: optimization(), 59 | module: { 60 | rules: [ 61 | { 62 | test: /\.(ts|js)x?$/, 63 | exclude: /node_modules/, 64 | use: [{ loader: 'babel-loader' }] 65 | }, 66 | { 67 | test: /\.(png|svg|jpg|gif|ico)$/, 68 | use: [{ loader: 'file-loader' }] 69 | }, 70 | { 71 | test: /\.(woff|woff2|eot|ttf|otf)$/, 72 | use: [{ loader: 'file-loader' }] 73 | } 74 | ] 75 | }, 76 | devServer: { 77 | contentBase: path.join(__dirname, 'dist'), 78 | historyApiFallback: true, 79 | publicPath: '/' 80 | }, 81 | plugins: [ 82 | new Dotenv({ 83 | systemvars: true 84 | }), 85 | new HTMLWebpack({ 86 | template: './src/index.html', 87 | filename: 'index.html', 88 | favicon: './src/assets/images/favicon.png', 89 | minify: { collapseWhitespace: !isDevelopment } 90 | }), 91 | new BundleAnalyzer({ 92 | analyzerMode: 'disabled', 93 | generateStatsFile: false, 94 | statsOptions: { source: false } 95 | }) 96 | ] 97 | } 98 | --------------------------------------------------------------------------------