diff --git a/Controllers/AppUsersController.cs b/Controllers/AppUsersController.cs index 8aa6105..372c1ac 100644 --- a/Controllers/AppUsersController.cs +++ b/Controllers/AppUsersController.cs @@ -13,7 +13,7 @@ using TicketManager.Resources; namespace TicketManager.Controllers { - [Authorize] + // [Authorize] [Produces("application/json")] [Route("api/v1/users")] [ApiController] diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index c06bfb0..0bd41d0 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -13,7 +13,7 @@ using System; namespace TicketManager.Controllers { // [Authorize(Roles = "Admin")] - [Authorize] + // [Authorize] [Produces("application/json")] [Route("api/v1/[controller]")] [ApiController] diff --git a/Controllers/TicketsController.cs b/Controllers/TicketsController.cs index 78891dd..efc6132 100644 --- a/Controllers/TicketsController.cs +++ b/Controllers/TicketsController.cs @@ -10,7 +10,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { - [Authorize] + // [Authorize] [Route("api/v1/[controller]")] [ApiController] public class TicketsController : ControllerBase diff --git a/README.md b/README.md index 3b74326..273ea67 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,10 @@ - [x] Form validators - [x] API deployed to Azure - [x] Front-end deployment: build first then use ssh … +- [x] Azure +- [ ] Refactor TabPanels code +- [ ] Refactor Lists code +- [ ] Query project members in UserPage +- [ ] Query progression info in UserPage +- [x] Add info fields in New Ticket Form +- [ ] Filter users in Users Modal diff --git a/Startup.cs b/Startup.cs index e358b50..271e797 100644 --- a/Startup.cs +++ b/Startup.cs @@ -63,7 +63,7 @@ namespace TicketManager { Name = "Ruidy Nemausat", Email = "ruidy.nemausat@gmail.com", - Url = new Uri("https://ruidywebsite.herokuapp.com/"), + Url = new Uri("https://ruidyportfolio.herokuapp.com/"), } }); // Set the comments path for the Swagger JSON and UI. diff --git a/TicketManager.csproj b/TicketManager.csproj index a0edd98..32386f8 100644 --- a/TicketManager.csproj +++ b/TicketManager.csproj @@ -27,6 +27,8 @@ + + @@ -39,7 +41,6 @@ - diff --git a/app.db b/app.db new file mode 100644 index 0000000..a8c3bd0 Binary files /dev/null and b/app.db differ diff --git a/client/package-lock.json b/client/package-lock.json index a8c0141..378ba1e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1057,6 +1057,11 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -1282,6 +1287,93 @@ "@types/yargs": "^13.0.0" } }, + "@material-ui/core": { + "version": "4.9.8", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.8.tgz", + "integrity": "sha512-4cslpG6oLoPWUfwPkX+hvbak4hAGiOfgXOu/UIYeeMrtsTEebC0Mirjoby7zhS4ny86YI3rXEFW6EZDmlj5n5w==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.9.6", + "@material-ui/system": "^4.9.6", + "@material-ui/types": "^5.0.0", + "@material-ui/utils": "^4.9.6", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.2", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "^1.14.1", + "prop-types": "^15.7.2", + "react-is": "^16.8.0", + "react-transition-group": "^4.3.0" + } + }, + "@material-ui/icons": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz", + "integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, + "@material-ui/lab": { + "version": "4.0.0-alpha.47", + "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.47.tgz", + "integrity": "sha512-+WC3O0M/769D3nO9Rqupusc+lob7tQMe5/DnOjAhZ0bpXlJbhZb7N84WkEk4JgQLj6ydP8e9Jhqd1lG+mGj+xw==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.9.6", + "clsx": "^1.0.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + } + }, + "@material-ui/styles": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.6.tgz", + "integrity": "sha512-ijgwStEkw1OZ6gCz18hkjycpr/3lKs1hYPi88O/AUn4vMuuGEGAIrqKVFq/lADmZUNF3DOFIk8LDkp7zmjPxtA==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "^5.0.0", + "@material-ui/utils": "^4.9.6", + "clsx": "^1.0.2", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.0.3", + "jss-plugin-camel-case": "^10.0.3", + "jss-plugin-default-unit": "^10.0.3", + "jss-plugin-global": "^10.0.3", + "jss-plugin-nested": "^10.0.3", + "jss-plugin-props-sort": "^10.0.3", + "jss-plugin-rule-value-function": "^10.0.3", + "jss-plugin-vendor-prefixer": "^10.0.3", + "prop-types": "^15.7.2" + } + }, + "@material-ui/system": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.6.tgz", + "integrity": "sha512-QtfoAePyqXoZ2HUVSwGb1Ro0kucMCvVjbI0CdYIR21t0Opgfm1Oer6ni9P5lfeXA39xSt0wCierw37j+YES48Q==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.9.6", + "prop-types": "^15.7.2" + } + }, + "@material-ui/types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz", + "integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==" + }, + "@material-ui/utils": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.6.tgz", + "integrity": "sha512-gqlBn0JPPTUZeAktn1rgMcy9Iczrr74ecx31tyZLVGdBGGzsxzM6PP6zeS7FuoLS6vG4hoZP7hWnOoHtkR0Kvw==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -1622,6 +1714,22 @@ "@types/react-router": "*" } }, + "@types/react-swipeable-views": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@types/react-swipeable-views/-/react-swipeable-views-0.13.0.tgz", + "integrity": "sha512-orrreCcXev6IUXDuHf07RDDCAoIZRMSr95eyWmYNRfjic7w/O+68iPu0NCysVls+UygRNvoqZMuXI72N/58E1w==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.4.tgz", + "integrity": "sha512-8DMUaDqh0S70TjkqU0DxOu80tFUiiaS9rxkWip/nb7gtvAsbqOXm02UCmR8zdcjWujgeYPiPNTVpVpKzUDotwA==", + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -3168,6 +3276,11 @@ "shallow-clone": "^0.1.2" } }, + "clsx": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", + "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3665,6 +3778,15 @@ "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=" }, + "css-vendor": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz", + "integrity": "sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==", + "requires": { + "@babel/runtime": "^7.6.2", + "is-in-browser": "^1.0.2" + } + }, "css-what": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", @@ -4091,6 +4213,30 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", + "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^2.6.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } + } + }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -6072,6 +6218,11 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, + "hyphenate-style-name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6371,6 +6522,11 @@ "is-extglob": "^2.1.1" } }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -7780,6 +7936,83 @@ "verror": "1.10.0" } }, + "jss": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz", + "integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-camel-case": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz", + "integrity": "sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.1.1" + } + }, + "jss-plugin-default-unit": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz", + "integrity": "sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.1.1" + } + }, + "jss-plugin-global": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz", + "integrity": "sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.1.1" + } + }, + "jss-plugin-nested": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz", + "integrity": "sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.1.1", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-props-sort": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz", + "integrity": "sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.1.1" + } + }, + "jss-plugin-rule-value-function": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz", + "integrity": "sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.1.1" + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz", + "integrity": "sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.7", + "jss": "10.1.1" + } + }, "jsx-ast-utils": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", @@ -7789,6 +8022,11 @@ "object.assign": "^4.1.0" } }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -9204,6 +9442,11 @@ "ts-pnp": "^1.1.2" } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, "portfinder": { "version": "1.0.25", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", @@ -10620,6 +10863,16 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.5.tgz", "integrity": "sha512-+DMR2k5c6BqMDSMF8hLH0vYKtKTeikiFW+fj0LClN+XZg4N9b8QUAdHC62CGWNLTi/gnuuemNcNcTFrCvK1f+A==" }, + "react-event-listener": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.6.tgz", + "integrity": "sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==", + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, "react-is": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", @@ -10731,6 +10984,96 @@ "workbox-webpack-plugin": "4.3.1" } }, + "react-swipeable-views": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/react-swipeable-views/-/react-swipeable-views-0.13.9.tgz", + "integrity": "sha512-WXC2FKYvZ9QdJ31v9LjEJEl1bA7E4AcaloTkbW0uU0dYf5uvv4aOpiyxubvOkVl1a5L2UAHmKSif4TmJ9usrSg==", + "requires": { + "@babel/runtime": "7.0.0", + "prop-types": "^15.5.4", + "react-swipeable-views-core": "^0.13.7", + "react-swipeable-views-utils": "^0.13.9", + "warning": "^4.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "react-swipeable-views-core": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/react-swipeable-views-core/-/react-swipeable-views-core-0.13.7.tgz", + "integrity": "sha512-ekn9oDYfBt0oqJSGGwLEhKvn+QaqMGTy//9dURTLf+vp7W5j6GvmKryYdnwJCDITaPFI2hujXV4CH9krhvaE5w==", + "requires": { + "@babel/runtime": "7.0.0", + "warning": "^4.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "react-swipeable-views-utils": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/react-swipeable-views-utils/-/react-swipeable-views-utils-0.13.9.tgz", + "integrity": "sha512-QLGxRKrbJCbWz94vkWLzb1Daaa2Y/TZKmsNKQ6WSNrS+chrlfZ3z9tqZ7YUJlW6pRWp3QZdLSY3UE3cN0TXXmw==", + "requires": { + "@babel/runtime": "7.0.0", + "keycode": "^2.1.7", + "prop-types": "^15.6.0", + "react-event-listener": "^0.6.0", + "react-swipeable-views-core": "^0.13.7", + "shallow-equal": "^1.2.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" + } + } + }, + "react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -11506,6 +11849,11 @@ } } }, + "shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -12888,6 +13236,14 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", diff --git a/client/package.json b/client/package.json index 87298e4..eb38d0b 100644 --- a/client/package.json +++ b/client/package.json @@ -4,6 +4,9 @@ "private": true, "dependencies": { "@auth0/auth0-spa-js": "^1.6.4", + "@material-ui/core": "^4.9.8", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.47", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^7.2.1", @@ -13,12 +16,14 @@ "@types/react": "^16.9.19", "@types/react-dom": "^16.9.5", "@types/react-router-dom": "^5.1.3", + "@types/react-swipeable-views": "^0.13.0", "@types/underscore": "^1.9.4", "history": "^4.10.1", "react": "^16.12.0", "react-dom": "^16.12.0", "react-router-dom": "^5.1.2", "react-scripts": "3.3.1", + "react-swipeable-views": "^0.13.9", "typescript": "^3.7.5", "underscore": "^1.9.2" }, diff --git a/client/public/index.html b/client/public/index.html index aec8e99..eb30249 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -12,11 +12,11 @@ - - + --> { +export const history = createHistory.createBrowserHistory(); + +export default function App() { const { loading } = useAuth0(); if (loading) { @@ -10,10 +16,10 @@ const App: FC = () => { } return ( -
- -
+ + + + + ); -}; - -export default App; +} diff --git a/client/src/VM/UserVM.ts b/client/src/VM/UserVM.ts index 5152faf..0ddbfcf 100644 --- a/client/src/VM/UserVM.ts +++ b/client/src/VM/UserVM.ts @@ -16,8 +16,9 @@ export class UserVM { public projects: Project[]; public tickets: Ticket[]; public activities: Activity[]; + public allUsers: User[]; - public constructor(user: User) { + public constructor(user: User, allUsers: User[]) { this.id = user.id; this.firstName = user.firstName; this.lastName = user.lastName; @@ -30,5 +31,6 @@ export class UserVM { this.projects = user.projects; this.tickets = user.tickets; this.activities = user.activities; + this.allUsers = allUsers; } } diff --git a/client/src/authentication/auth0.jsx b/client/src/authentication/auth0.jsx new file mode 100644 index 0000000..aa5194a --- /dev/null +++ b/client/src/authentication/auth0.jsx @@ -0,0 +1,90 @@ +// src/react-auth0-spa.js +import React, { useState, useEffect, useContext } from "react"; +import createAuth0Client from "@auth0/auth0-spa-js"; + +const DEFAULT_REDIRECT_CALLBACK = () => + window.history.replaceState({}, document.title, window.location.pathname); + +export const Auth0Context = React.createContext(); +export const useAuth0 = () => useContext(Auth0Context); +export const Auth0Provider = ({ + children, + onRedirectCallback = DEFAULT_REDIRECT_CALLBACK, + ...initOptions +}) => { + const [isAuthenticated, setIsAuthenticated] = useState(); + const [user, setUser] = useState(); + const [auth0Client, setAuth0] = useState(); + const [loading, setLoading] = useState(true); + const [popupOpen, setPopupOpen] = useState(false); + + useEffect(() => { + const initAuth0 = async () => { + const auth0FromHook = await createAuth0Client(initOptions); + setAuth0(auth0FromHook); + + if ( + window.location.search.includes("code=") && + window.location.search.includes("state=") + ) { + const { appState } = await auth0FromHook.handleRedirectCallback(); + onRedirectCallback(appState); + } + + const isAuthenticated = await auth0FromHook.isAuthenticated(); + + setIsAuthenticated(isAuthenticated); + + if (isAuthenticated) { + const user = await auth0FromHook.getUser(); + setUser(user); + } + + setLoading(false); + }; + initAuth0(); + // eslint-disable-next-line + }, []); + + const loginWithPopup = async (params = {}) => { + setPopupOpen(true); + try { + await auth0Client.loginWithPopup(params); + } catch (error) { + console.error(error); + } finally { + setPopupOpen(false); + } + const user = await auth0Client.getUser(); + setUser(user); + setIsAuthenticated(true); + }; + + const handleRedirectCallback = async () => { + setLoading(true); + await auth0Client.handleRedirectCallback(); + const user = await auth0Client.getUser(); + setLoading(false); + setIsAuthenticated(true); + setUser(user); + }; + return ( + auth0Client.getIdTokenClaims(...p), + loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p), + getTokenSilently: (...p) => auth0Client.getTokenSilently(...p), + getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p), + logout: (...p) => auth0Client.logout(...p) + }} + > + {children} + + ); +}; diff --git a/client/src/authentication/auth0.tsx b/client/src/authentication/auth0.tsx deleted file mode 100644 index bb85f11..0000000 --- a/client/src/authentication/auth0.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState, useEffect, useContext, FC } from "react"; -import createAuth0Client from "@auth0/auth0-spa-js"; -import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client"; - -interface IAuth0Context { - isAuthenticated: boolean; - user: any; - loading: boolean; - popupOpen: boolean; - loginWithPopup(options: PopupLoginOptions): Promise; - handleRedirectCallback(): Promise; - getIdTokenClaims(o?: getIdTokenClaimsOptions): Promise; - loginWithRedirect(o: RedirectLoginOptions): Promise; - getTokenSilently(o?: GetTokenSilentlyOptions): Promise; - getTokenWithPopup(o?: GetTokenWithPopupOptions): Promise; - logout(o?: LogoutOptions): void; -} -export interface IAuth0ProviderOptions { - children: React.ReactElement; - onRedirectCallback?(result: RedirectLoginResult): void; -} - -const DEFAULT_REDIRECT_CALLBACK: () => void = () => - window.history.replaceState({}, document.title, window.location.pathname); - -export const Auth0Context: React.Context = React.createContext( - null -); -export const useAuth0: () => IAuth0Context = () => useContext(Auth0Context)!; -export const Auth0Provider = ({ - children, - onRedirectCallback = DEFAULT_REDIRECT_CALLBACK, - ...initOptions -}: IAuth0ProviderOptions & Auth0ClientOptions) => { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [user, setUser] = useState(); - const [auth0Client, setAuth0] = useState(); - const [loading, setLoading] = useState(true); - const [popupOpen, setPopupOpen] = useState(false); - - useEffect(() => { - const initAuth0 = async () => { - const auth0FromHook = await createAuth0Client(initOptions); - setAuth0(auth0FromHook); - - if (window.location.search.includes("code=")) { - const { appState } = await auth0FromHook.handleRedirectCallback(); - onRedirectCallback(appState); - } - - const isAuthenticated = await auth0FromHook.isAuthenticated(); - - setIsAuthenticated(isAuthenticated); - - if (isAuthenticated) { - const user = await auth0FromHook.getUser(); - setUser(user); - } - - setLoading(false); - }; - initAuth0(); - // eslint-disable-next-line - }, []); - - const loginWithPopup = async (o: PopupLoginOptions) => { - setPopupOpen(true); - try { - await auth0Client!.loginWithPopup(o); - } catch (error) { - console.error(error); - } finally { - setPopupOpen(false); - } - const user = await auth0Client!.getUser(); - setUser(user); - setIsAuthenticated(true); - }; - - const handleRedirectCallback = async () => { - setLoading(true); - const result = await auth0Client!.handleRedirectCallback(); - const user = await auth0Client!.getUser(); - setLoading(false); - setIsAuthenticated(true); - setUser(user); - return result; - }; - return ( - - auth0Client!.getIdTokenClaims(o), - loginWithRedirect: (o: RedirectLoginOptions) => - auth0Client!.loginWithRedirect(o), - getTokenSilently: (o: GetTokenSilentlyOptions | undefined) => - auth0Client!.getTokenSilently(o), - getTokenWithPopup: (o: GetTokenWithPopupOptions | undefined) => - auth0Client!.getTokenWithPopup(o), - logout: (o: LogoutOptions | undefined) => auth0Client!.logout(o) - }} - > - {children} - - ); -}; diff --git a/client/src/components/AppFileList.tsx b/client/src/components/AppFileList.tsx deleted file mode 100644 index fd4cd63..0000000 --- a/client/src/components/AppFileList.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; -import { AppFile } from "../types/AppFile"; -import { FileCollection } from "./FileCollection"; -import { InputFile } from "./InputFile"; -import { FilterBar } from "./FilterBar"; - -type IProps = { - files: AppFile[]; -}; - -export const FileList: FC = ({ files }) => { - const [filterText, setFilterText] = useState(""); - const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { - setFilterText(""); - }; - const handleChange: (e: ChangeEvent) => void = ( - e: ChangeEvent - ) => { - setFilterText(e.target.value); - }; - return ( - <> -
-

Files

- -
- - - - ); -}; diff --git a/client/src/components/Avatar.tsx b/client/src/components/Avatar.tsx deleted file mode 100644 index 91dd6e9..0000000 --- a/client/src/components/Avatar.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { FC } from "react"; - -interface IProps { - picture: string; -} -export const Avatar: FC = ({ picture }) => { - return ( - <> - user avatar - - ); -}; diff --git a/client/src/components/AvatarList.tsx b/client/src/components/AvatarList.tsx deleted file mode 100644 index 606111f..0000000 --- a/client/src/components/AvatarList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FC } from "react"; -import { User } from "../types/User"; -import { Link } from "react-router-dom"; - -interface AvatarListProps { - users: User[]; -} - -export const AvatarList: FC = ({ users }) => { - return users === undefined ? ( - <> - ) : ( - <> - {users.map((user: User, i: number) => ( - - {user.fullName} - - ))} - - ); -}; diff --git a/client/src/components/Avatars/AvatarList.tsx b/client/src/components/Avatars/AvatarList.tsx new file mode 100644 index 0000000..5c1cf8a --- /dev/null +++ b/client/src/components/Avatars/AvatarList.tsx @@ -0,0 +1,36 @@ +import React, { FC } from "react"; +import { Link } from "react-router-dom"; +import Avatar from "@material-ui/core/Avatar"; +import AvatarGroup from "@material-ui/lab/AvatarGroup"; +import { User } from "../../types/User"; +import { makeStyles, Theme, createStyles } from "@material-ui/core"; + +interface AvatarListProps { + users: User[]; +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + }, + }) +); + +export const AvatarList: FC = ({ users }) => { + const classes = useStyles(); + return users === undefined ? ( + <> + ) : ( +
+ + {users.map((user: User, i: number) => ( + + + + ))} + +
+ ); +}; diff --git a/client/src/components/Avatars/UserAvatar.tsx b/client/src/components/Avatars/UserAvatar.tsx new file mode 100644 index 0000000..6bbd826 --- /dev/null +++ b/client/src/components/Avatars/UserAvatar.tsx @@ -0,0 +1,37 @@ +import React, { FC } from "react"; +import { makeStyles, createStyles, Theme } from "@material-ui/core/styles"; +import Avatar from "@material-ui/core/Avatar"; + +interface IProps { + picture: string; + alt: string; +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + display: "flex", + "& > *": { + margin: theme.spacing(1) + } + }, + small: { + width: theme.spacing(3), + height: theme.spacing(3) + }, + large: { + width: theme.spacing(10), + height: theme.spacing(10) + } + }) +); + +export const UserAvatar: FC = ({ picture, alt }) => { + const classes = useStyles(); + + return ( +
+ +
+ ); +}; diff --git a/client/src/components/ButtonAppBar.tsx b/client/src/components/ButtonAppBar.tsx new file mode 100644 index 0000000..d652d15 --- /dev/null +++ b/client/src/components/ButtonAppBar.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; +import AppBar from "@material-ui/core/AppBar"; +import Toolbar from "@material-ui/core/Toolbar"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import IconButton from "@material-ui/core/IconButton"; +import MenuIcon from "@material-ui/icons/Menu"; +import { useAuth0 } from "../authentication/auth0"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + flexGrow: 1 + }, + menuButton: { + marginRight: theme.spacing(2) + }, + title: { + flexGrow: 1 + } + }) +); + +export default function ButtonAppBar() { + const classes = useStyles(); + const { isAuthenticated, loginWithRedirect, logout } = useAuth0(); + + return ( +
+ + + + + + + + + {!isAuthenticated ? ( + + ) : ( + + )} + + +
+ ); +} diff --git a/client/src/components/Button.tsx b/client/src/components/Buttons/Button.tsx similarity index 100% rename from client/src/components/Button.tsx rename to client/src/components/Buttons/Button.tsx diff --git a/client/src/components/Buttons/FloatingButton.tsx b/client/src/components/Buttons/FloatingButton.tsx new file mode 100644 index 0000000..ea28e6d --- /dev/null +++ b/client/src/components/Buttons/FloatingButton.tsx @@ -0,0 +1,26 @@ +import React, { FC, MouseEvent } from "react"; +import { Fab } from "@material-ui/core"; +import AddIcon from "@material-ui/icons/Add"; + +interface IProps { + icon?: string; + color?: "inherit" | "primary" | "secondary" | "default" | undefined; + onClick?: (e: MouseEvent) => void; + size?: "small" | "medium" | "large" | undefined; + text?: string; +} + +export const FloatingButton: FC = ({ + color, + icon, + size, + text, + onClick +}) => { + return ( + + + {text} + + ); +}; diff --git a/client/src/components/Cards/HorizontalCard.tsx b/client/src/components/Cards/HorizontalCard.tsx new file mode 100644 index 0000000..d915df5 --- /dev/null +++ b/client/src/components/Cards/HorizontalCard.tsx @@ -0,0 +1,48 @@ +import React, { FC, ReactNode } from "react"; +import { Link } from "react-router-dom"; +import { makeStyles } from "@material-ui/core/styles"; +import Card from "@material-ui/core/Card"; +import CardActions from "@material-ui/core/CardActions"; +import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; +import { ProgressBar } from "../Progress/ProgressBar"; + +interface IProps { + title?: string; + link?: string; + progress?: number; + content: ReactNode; + actions?: ReactNode; +} + +const useStyles = makeStyles({ + root: { + minWidth: 275, + marginBottom: 20, + }, +}); + +export const HorizontalCard: FC = ({ + title, + link = "#", + content, + actions, + progress = 0, +}) => { + const classes = useStyles(); + + return ( + + + + + + {title ?? "Nothing to do"} + + + {content} + + {actions} + + ); +}; diff --git a/client/src/components/Cards/ProjectCard.tsx b/client/src/components/Cards/ProjectCard.tsx new file mode 100644 index 0000000..3f3fe9a --- /dev/null +++ b/client/src/components/Cards/ProjectCard.tsx @@ -0,0 +1,63 @@ +import React, { FC } from "react"; +import { HorizontalCard } from "./HorizontalCard"; +import { makeStyles, Theme, createStyles } from "@material-ui/core"; +import { AvatarList } from "../Avatars/AvatarList"; +import { ProgressInfo } from "../Progress/ProgressInfo"; +import { User } from "../../types/User"; +import { getRemainingdays } from "../../utils/methods"; + +interface IProps { + title?: string; + remainingDays?: string; + link?: string; + members?: User[]; + progress?: number; + ticketsNumber?: number; + ticketsDone?: number; +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + progress: { + paddingTop: theme.spacing(2), + }, + }) +); + +const ProjectCard: FC = ({ + title, + remainingDays = "", + link = "#", + members, + progress = 0, + ticketsNumber, + ticketsDone, +}) => { + const classes = useStyles(); + + const Content: FC = () => { + return ( + <> + {members && } +
+ +
+ + ); + }; + + return ( + } + progress={progress} + /> + ); +}; + +export default ProjectCard; diff --git a/client/src/components/Cards/TicketCard.tsx b/client/src/components/Cards/TicketCard.tsx new file mode 100644 index 0000000..9018c84 --- /dev/null +++ b/client/src/components/Cards/TicketCard.tsx @@ -0,0 +1,65 @@ +import React, { FC, MouseEvent } from "react"; +import { Button, Typography, Grid } from "@material-ui/core"; +import { HorizontalCard } from "./HorizontalCard"; +import TicketChipsArray from "./TicketChipsArray"; +import { Ticket } from "../../types/Ticket"; +import { getRemainingdays } from "../../utils/methods"; + +interface IProps { + ticket?: Ticket; + validateTicket?: (event: MouseEvent) => void; + link?: string; +} + +const TicketCard: FC = ({ + link = "#", + validateTicket, + ticket = {} as Ticket, +}) => { + const Content: FC = () => { + return ( + + + + + Due in{" "} + {ticket?.endingDate ? ( + getRemainingdays(ticket?.endingDate) + ) : ( + + Too much 0 + + )}{" "} + days + + + + ); + }; + + const Action = () => { + return ( + <> + + + ); + }; + + return ( + } + actions={} + /> + ); +}; + +export default TicketCard; diff --git a/client/src/components/Cards/TicketChipsArray.tsx b/client/src/components/Cards/TicketChipsArray.tsx new file mode 100644 index 0000000..ec50e81 --- /dev/null +++ b/client/src/components/Cards/TicketChipsArray.tsx @@ -0,0 +1,76 @@ +import React, { FC } from "react"; +import { Grid, Chip, makeStyles, Theme, createStyles } from "@material-ui/core"; +import CategoryIcon from "@material-ui/icons/Category"; +import PriorityHighIcon from "@material-ui/icons/PriorityHigh"; +import SpeedIcon from "@material-ui/icons/Speed"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + display: "flex", + justifyContent: "space-between", + flexWrap: "wrap", + listStyle: "none", + padding: theme.spacing(0.5), + margin: 0, + marginTop: 20, + marginBottom: 20, + }, + chip: { + margin: theme.spacing(0.5), + }, + }) +); + +interface IProps { + status: string; + category: string; + impact: string; + difficulty: string; +} +const TicketChipsArray: FC = ({ + status, + category, + impact, + difficulty, +}) => { + const classes = useStyles(); + const chipData = [ + // { label: "status", value: status }, + { label: "category", value: category }, + { label: "impact", value: impact }, + { label: "difficulty", value: difficulty }, + ]; + + return ( + // + + {chipData.map((data, i: number) => { + let icon = ; + let color: "inherit" | "default" | "primary" | "secondary" | undefined; + + if (data.label === "impact") { + color = "primary"; + icon = ; + } + if (data.label === "difficulty") { + color = "secondary"; + icon = ; + } + + return ( +
  • + +
  • + ); + })} +
    + ); +}; + +export default TicketChipsArray; diff --git a/client/src/components/FileCollection.tsx b/client/src/components/FileCollection.tsx index c709da9..4f25ec3 100644 --- a/client/src/components/FileCollection.tsx +++ b/client/src/components/FileCollection.tsx @@ -1,4 +1,11 @@ import React, { FC } from "react"; +import { createStyles, Theme, makeStyles } from "@material-ui/core/styles"; +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import ListItemText from "@material-ui/core/ListItemText"; +import ListItemAvatar from "@material-ui/core/ListItemAvatar"; +import Avatar from "@material-ui/core/Avatar"; +import WorkIcon from "@material-ui/icons/Work"; import { AppFile } from "../types/AppFile"; type IProps = { @@ -6,24 +13,32 @@ type IProps = { filterText: string; }; +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: "100%", + maxWidth: 360, + backgroundColor: theme.palette.background.paper + } + }) +); + export const FileCollection: FC = ({ files, filterText }) => { - console.log(); + const classes = useStyles(); return ( - <> -
      - {files.length === 0 ? ( - - ) : ( - files - .filter( - f => - f.name.toLowerCase().includes(filterText.toLowerCase()) || - f.format.toLowerCase().includes(filterText.toLowerCase()) - ) - .map((file: AppFile) => ) - )} -
    - + + {files.length === 0 ? ( + + ) : ( + files + .filter( + f => + f.name.toLowerCase().includes(filterText.toLowerCase()) || + f.format.toLowerCase().includes(filterText.toLowerCase()) + ) + .map((file: AppFile) => ) + )} + ); }; @@ -33,16 +48,16 @@ type IFProps = { export const FileEntry: FC = ({ file }) => { return ( -
  • - {/* */} - folder - {file ? file.name : "Add your first file"} -

    - {file ? file.size : 0}kb {file ? file.format : "pdf"} -

    - - more_vert - -
  • + + + + + + + + ); }; diff --git a/client/src/components/FilterBar.tsx b/client/src/components/FilterBar.tsx index fc9cb9c..bae32cb 100644 --- a/client/src/components/FilterBar.tsx +++ b/client/src/components/FilterBar.tsx @@ -1,5 +1,9 @@ import React, { FC, ChangeEvent, MouseEvent } from "react"; import { useRouteMatch } from "react-router-dom"; +import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; +import TextField from "@material-ui/core/TextField"; +import { Grid } from "@material-ui/core"; +// import { AccountCircle, FilterList, FilterListSharp } from "@material-ui/icons"; type IProps = { filterText: string; @@ -7,35 +11,47 @@ type IProps = { clearFilterText: (e: MouseEvent) => void; }; +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + "& > *": { + margin: theme.spacing(1), + width: "25ch", + }, + }, + margin: { + margin: theme.spacing(1), + }, + filter: { + backgroundColor: "#fff", + }, + }) +); + export const FilterBar: FC = ({ filterText, handleChange, - clearFilterText + // clearFilterText }) => { const { url } = useRouteMatch(); - const placeholder: string = url.split("/")[3] || "users"; + const placeholder: string = url.split("/")[3] || "elements"; + const classes = useStyles(); return ( - <> -
    -
    - + + + - - - close - -
    -
    - + + + ); }; diff --git a/client/src/components/FloatingButton.tsx b/client/src/components/FloatingButton.tsx deleted file mode 100644 index 0031443..0000000 --- a/client/src/components/FloatingButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React, { FC, MouseEvent } from "react"; -import { Button } from "./Button"; - -interface IProps { - icon?: string; - size?: string; - color?: string; - onClick?: (e: MouseEvent) => void; -} - -export const FloatingButton: FC = ({ - icon = "add", - size = "small", - color = "red", - onClick -}) => { - const iconComponent = {icon}; - return ( - - ); -}; diff --git a/client/src/components/Footer.tsx b/client/src/components/Footer.tsx new file mode 100644 index 0000000..09a94eb --- /dev/null +++ b/client/src/components/Footer.tsx @@ -0,0 +1,61 @@ +import React, { FC } from "react"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/core/styles"; +import Container from "@material-ui/core/Container"; +import Link from "@material-ui/core/Link"; + +interface IProps { + brand: string; + text: string; +} + +const copyParams: IProps = { + brand: "BugBuster", + text: "Made with 🔥" +}; + +const Copyright: FC = ({ brand, text }) => { + return ( + + {"© "} + + {brand} + {" "} + {new Date().getFullYear()} + {`. All Rights Reserved. ${text}`} + + ); +}; + +const useStyles = makeStyles(theme => ({ + footer: { + padding: theme.spacing(3, 2), + marginTop: "auto", + backgroundColor: + theme.palette.type === "light" + ? theme.palette.grey[200] + : theme.palette.grey[800] + } +})); + +export default function Footer() { + const classes = useStyles(); + + return ( +
    + + + + Ruidy Nemausat + {" "} + + + +
    + ); +} diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 863906a..a93d504 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -1,4 +1,5 @@ import React, { FC } from "react"; +import { Typography, Box } from "@material-ui/core"; type HeaderProps = { title: string; @@ -7,9 +8,13 @@ type HeaderProps = { export const Header: FC = ({ title, description }) => { return ( - <> -

    {title}

    -

    {description}

    - + + + {title} + + + {description} + + ); }; diff --git a/client/src/components/HorizontalCard.tsx b/client/src/components/HorizontalCard.tsx deleted file mode 100644 index 8d53495..0000000 --- a/client/src/components/HorizontalCard.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { FC, MouseEvent } from "react"; -import { Link } from "react-router-dom"; -import { getRemainingdays } from "../utils/methods"; - -interface IProps { - title?: string; - remainingDays?: string; - validateTicket?: (event: MouseEvent) => void; - link?: string; -} - -export const HorizontalCard: FC = ({ - title, - remainingDays, - link = "#", - validateTicket -}) => { - return ( -
  • -
    -
    -
    -
    -
    -
    - - {title ?? "Nothing to do"} - -
    -
    - - Due{" "} - {remainingDays ? ( - getRemainingdays(remainingDays) - ) : ( - - Too much 0 - - )}{" "} - days - -
    - - - check - - - {/* - - archive - - */} -
    -
    -
    -
    -
    -
  • - ); -}; diff --git a/client/src/components/InputFile.tsx b/client/src/components/InputFile.tsx index 1f8c63f..c4257e7 100644 --- a/client/src/components/InputFile.tsx +++ b/client/src/components/InputFile.tsx @@ -1,29 +1,62 @@ import React, { FC } from "react"; +import { CloudUpload } from "@material-ui/icons"; +import { makeStyles, createStyles, Theme } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; -type IProps = {}; - -export const InputFile: FC = () => { +export const InputFile: FC = () => { return ( <>
    -
    - cloud_upload + + -
    -
    - -
    +
    ); }; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + "& > *": { + margin: theme.spacing(1) + } + }, + input: { + display: "none" + } + }) +); + +const UploadButton: FC = () => { + const classes = useStyles(); + + return ( +
    + + +
    + ); +}; diff --git a/client/src/components/ActivityList.tsx b/client/src/components/Lists/ActivityList.tsx similarity index 73% rename from client/src/components/ActivityList.tsx rename to client/src/components/Lists/ActivityList.tsx index 5f597c0..c70082c 100644 --- a/client/src/components/ActivityList.tsx +++ b/client/src/components/Lists/ActivityList.tsx @@ -1,7 +1,7 @@ import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; -import { ActivityCollection } from "./ActivityCollection"; -import { Activity } from "../types/Activity"; -import { FilterBar } from "./FilterBar"; +import { ActivityCollection } from "../ActivityCollection"; +import { Activity } from "../../types/Activity"; +import { FilterBar } from "../FilterBar"; type IProps = { activities: Activity[]; @@ -12,9 +12,7 @@ export const ActivityList: FC = ({ activities }) => { const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { setFilterText(""); }; - const handleChange: (e: ChangeEvent) => void = ( - e: ChangeEvent - ) => { + const handleChange = (e: ChangeEvent) => { setFilterText(e.target.value); }; diff --git a/client/src/components/Lists/AppFileList.tsx b/client/src/components/Lists/AppFileList.tsx new file mode 100644 index 0000000..028d18b --- /dev/null +++ b/client/src/components/Lists/AppFileList.tsx @@ -0,0 +1,40 @@ +import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; +import { AppFile } from "../../types/AppFile"; +import { FileCollection } from "../FileCollection"; +import { InputFile } from "../InputFile"; +import { FilterBar } from "../FilterBar"; +import { Grid, Typography } from "@material-ui/core"; + +type IProps = { + files: AppFile[]; +}; + +export const FileList: FC = ({ files }) => { + const [filterText, setFilterText] = useState(""); + const clearFilterText = (e: MouseEvent): void => { + setFilterText(""); + }; + const handleChange = (e: ChangeEvent): void => { + setFilterText(e.target.value); + }; + return ( + <> + + + + Files + + + + + + + + + + ); +}; diff --git a/client/src/components/MemberList.tsx b/client/src/components/Lists/MemberList.tsx similarity index 70% rename from client/src/components/MemberList.tsx rename to client/src/components/Lists/MemberList.tsx index 4f49466..631fe80 100644 --- a/client/src/components/MemberList.tsx +++ b/client/src/components/Lists/MemberList.tsx @@ -1,7 +1,7 @@ import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; -import { UsersModalEntry } from "./UsersModalEntry"; -import { FilterBar } from "./FilterBar"; -import { User } from "../types/User"; +import { UsersModalEntry } from "../Modals/UsersModalEntry"; +import { FilterBar } from "../FilterBar"; +import { User } from "../../types/User"; interface IProps { users: User[]; @@ -10,12 +10,10 @@ interface IProps { export const MemberList: FC = ({ users }) => { const [members, setMembers] = useState([]); const [filterText, setFilterText] = useState(""); - const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { + const clearFilterText = (e: MouseEvent): void => { setFilterText(""); }; - const handleChange: (e: ChangeEvent) => void = ( - e: ChangeEvent - ) => { + const handleChange = (e: ChangeEvent): void => { setFilterText(e.target.value); }; return ( diff --git a/client/src/components/Lists/ProjectList.tsx b/client/src/components/Lists/ProjectList.tsx new file mode 100644 index 0000000..873e83a --- /dev/null +++ b/client/src/components/Lists/ProjectList.tsx @@ -0,0 +1,111 @@ +import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; +import { + Typography, + Grid, + makeStyles, + createStyles, + Theme, +} from "@material-ui/core"; +import { FilterBar } from "../FilterBar"; +import ProjectCard from "../Cards/ProjectCard"; +import { FloatingButton } from "../Buttons/FloatingButton"; +import { NewProjectModal } from "../Modals/NewProjectModal"; +import { Project } from "../../types/Project"; +import { User } from "../../types/User"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + header: { + paddingBottom: theme.spacing(2), + }, + addButton: { + position: "relative", + marginLeft: "20px", + }, + }) +); + +type IProps = { + projects: Project[]; + allUsers: User[]; +}; + +export const ProjectList: FC = ({ projects, allUsers }) => { + const [filterText, setFilterText] = useState(""); + const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { + setFilterText(""); + }; + const [showNew, setShowNew] = useState(false); + const onClick = (e: MouseEvent): void => { + e.preventDefault(); + setShowNew(true); + }; + const handleChange = (e: ChangeEvent): void => { + setFilterText(e.target.value); + }; + + let filteredProjects = projects.filter( + (t) => + t.status !== "Done" && + t.title.toLowerCase().includes(filterText.toLowerCase()) + ); + + const classes = useStyles(); + + return ( + <> + { + setShowNew(false); + }} + show={showNew} + allUsers={allUsers} + /> + + + + Projects + + + + + + + +
    + {filteredProjects.length === 0 ? ( + + ) : ( + filteredProjects.map((t: Project) => ( + t.status === "Done").length + } + /> + )) + )} +
    +
    +
    + + ); +}; diff --git a/client/src/components/Lists/TicketList.tsx b/client/src/components/Lists/TicketList.tsx new file mode 100644 index 0000000..e6910c5 --- /dev/null +++ b/client/src/components/Lists/TicketList.tsx @@ -0,0 +1,126 @@ +import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; +import { + Grid, + Typography, + makeStyles, + Theme, + createStyles, +} from "@material-ui/core"; +import { FloatingButton } from "../Buttons/FloatingButton"; +import { FilterBar } from "../FilterBar"; +import { HttpResponse } from "../../types/HttpResponse"; +import { Ticket } from "../../types/Ticket"; +import { NewTicketModal } from "../Modals/NewTicketModal"; +import { Project } from "../../types/Project"; +import { put } from "../../utils/http"; +import { Constants } from "../../utils/Constants"; +import TicketCard from "../Cards/TicketCard"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + header: { + paddingBottom: theme.spacing(2), + }, + addButton: { + position: "relative", + marginLeft: "20px", + }, + }) +); + +type TicketListProps = { + tickets: Ticket[]; + allProjects: Project[]; + addButton?: Boolean; +}; + +export const TicketList: FC = ({ + tickets, + allProjects, + addButton = true, +}) => { + const [filterText, setFilterText] = useState(""); + const clearFilterText = (e: MouseEvent): void => { + setFilterText(""); + }; + + const onClick = (e: MouseEvent): void => { + e.preventDefault(); + setShowNew(true); + }; + const handleChange = (e: ChangeEvent): void => { + setFilterText(e.target.value); + }; + + const [showNew, setShowNew] = useState(false); + let filteredTickets = tickets.filter( + (t) => + t.status !== "Done" && + t.title.toLowerCase().includes(filterText.toLowerCase()) + ); + + const classes = useStyles(); + + return ( + <> + { + setShowNew(false); + }} + show={showNew} + allProjects={allProjects} + /> + + + + + Tickets + {addButton && ( + + + + )} + + + + + +
    + {filteredTickets.length === 0 ? ( + + ) : ( + filteredTickets.map((t: Ticket) => ( + { + e.preventDefault(); + await put>( + `${Constants.ticketsURI}/${t.id}/closed`, + {} + ); + }} + /> + )) + )} +
    +
    +
    + + ); +}; diff --git a/client/src/components/LogInForm.tsx b/client/src/components/LogInForm.tsx index 9ef2fb0..4a85da7 100644 --- a/client/src/components/LogInForm.tsx +++ b/client/src/components/LogInForm.tsx @@ -1,7 +1,7 @@ import React, { FC } from "react"; import { InputField } from "./InputField"; import { PasswordField } from "./PasswordField"; -import { Button } from "./Button"; +import { Button } from "./Buttons/Button"; export const LogInForm: FC = () => { return ( diff --git a/client/src/components/Modal.tsx b/client/src/components/Modal.tsx deleted file mode 100644 index b28367f..0000000 --- a/client/src/components/Modal.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { FC, CSSProperties } from "react"; - -interface IProps { - handleClose: () => void; - show: boolean; -} -export const Modal: FC = ({ handleClose, show, children }) => { - const showHideStyle: CSSProperties = show - ? { display: "block", zIndex: 10 } - : { display: "none", zIndex: 10 }; - return ( -
    -
    {children}
    -
    - ); -}; diff --git a/client/src/components/Modals/Modal.tsx b/client/src/components/Modals/Modal.tsx new file mode 100644 index 0000000..705f172 --- /dev/null +++ b/client/src/components/Modals/Modal.tsx @@ -0,0 +1,79 @@ +import React, { FC } from "react"; +import Dialog from "@material-ui/core/Dialog"; +import { + DialogTitle, + Typography, + IconButton, + DialogContent, + makeStyles, + Theme, + createStyles, + DialogActions, + Button, +} from "@material-ui/core"; +import CloseIcon from "@material-ui/icons/Close"; + +interface IProps { + handleClose: () => void; + show: boolean; + action: string; + handleAction: (e: React.FormEvent) => Promise; + name: string; +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + }) +); + +export const Modal: FC = ({ + handleClose, + show, + action, + handleAction, + children, + name, +}) => { + const classes = useStyles(); + + return ( + + + {name} + {handleClose ? ( + + + + ) : null} + + {children} + + + + + + ); +}; diff --git a/client/src/components/Modals/NewProjectModal.tsx b/client/src/components/Modals/NewProjectModal.tsx new file mode 100644 index 0000000..3d63126 --- /dev/null +++ b/client/src/components/Modals/NewProjectModal.tsx @@ -0,0 +1,90 @@ +import React, { FC, useState, FormEvent } from "react"; +import { TextField } from "@material-ui/core"; +import { Modal } from "./Modal"; +import { Project } from "../../types/Project"; +import { User } from "../../types/User"; +import { post } from "../../utils/http"; +import { Constants } from "../../utils/Constants"; + +interface IProps { + show: boolean; + handleClose: () => void; + allUsers: User[]; +} + +export const NewProjectModal: FC = ({ show, handleClose }) => { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [endingDate, setEndingDate] = useState(""); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + let newProject = { + title: title, + description: description, + endingDate: new Date(endingDate).toISOString(), + managerId: "cd179eb7-3a54-4060-b22c-3e947bdffcbc", // get current User id + }; + + await post(`${Constants.projectsURI}`, newProject); + handleClose(); + }; + + return ( + + ) => + setTitle(e.target.value) + } + autoFocus + /> + + ) => + setDescription(e.target.value) + } + multiline + /> + + ) => + setEndingDate(e.target.value) + } + /> + + ); +}; diff --git a/client/src/components/Modals/NewTicketModal.tsx b/client/src/components/Modals/NewTicketModal.tsx new file mode 100644 index 0000000..9493b17 --- /dev/null +++ b/client/src/components/Modals/NewTicketModal.tsx @@ -0,0 +1,213 @@ +import React, { FC, useState, FormEvent } from "react"; +import { useRouteMatch } from "react-router-dom"; +import { + TextField, + MenuItem, + Grid, + makeStyles, + Theme, +} from "@material-ui/core"; +import { Modal } from "./Modal"; +import { Ticket } from "../../types/Ticket"; +import { Project } from "../../types/Project"; +import { post } from "../../utils/http"; +import { Constants } from "../../utils/Constants"; +import Category from "../../types/enums/category"; +import Impact from "../../types/enums/impact"; +import Difficulty from "../../types/enums/difficulty"; + +interface IProps { + show: boolean; + handleClose: () => void; + allProjects: Project[]; +} + +const useStyles = makeStyles((theme: Theme) => ({ + select: { + width: 120, + }, +})); + +export const NewTicketModal: FC = ({ + show, + handleClose, + allProjects, +}) => { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [endingDate, setEndingDate] = useState(""); + + const { url } = useRouteMatch(); + const id = url.split("/")[2]; + const [projectId, setProjectId] = useState(id); + const [categoryID, setCategoryID] = useState(0); + const [impactID, setImpactID] = useState(0); + const [difficultyID, setDifficultyID] = useState(0); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + let newTicket = { + title: title, + description: description, + endingDate: new Date(endingDate).toISOString(), + creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94", // get current User id + projectId: parseInt(projectId), + impact: impactID, + difficulty: difficultyID, + category: categoryID, + }; + + // const response: HttpResponse = + await post(`${Constants.ticketsURI}`, newTicket); + handleClose(); + }; + + const classes = useStyles(); + return ( + + ) => + setTitle(e.target.value) + } + autoFocus + /> + + ) => + setDescription(e.target.value) + } + multiline + /> + + ) => { + e.preventDefault(); + setProjectId(e.target.value); + }} + // helperText="Please select your currency" + variant="outlined" + margin="normal" + > + {allProjects.map((p) => ( + + {p.title} + + ))} + + + ) => + setEndingDate(e.target.value) + } + /> + + + ) => { + e.preventDefault(); + setCategoryID(parseInt(e.target.value)); + }} + variant="outlined" + margin="normal" + className={classes.select} + > + {Category.map((c: string, i: number) => ( + + {c} + + ))} + + + ) => { + e.preventDefault(); + setImpactID(parseInt(e.target.value)); + }} + variant="outlined" + margin="normal" + > + {Impact.map((c: string, i: number) => ( + + {c} + + ))} + + + ) => { + e.preventDefault(); + setDifficultyID(parseInt(e.target.value)); + }} + variant="outlined" + margin="normal" + > + {Difficulty.map((c: string, i: number) => ( + + {c} + + ))} + + + + ); +}; diff --git a/client/src/components/Modals/UsersModal.tsx b/client/src/components/Modals/UsersModal.tsx new file mode 100644 index 0000000..4b915f9 --- /dev/null +++ b/client/src/components/Modals/UsersModal.tsx @@ -0,0 +1,114 @@ +import React, { FC, useState, ChangeEvent, FormEvent } from "react"; +import { useParams } from "react-router-dom"; +import { Grid } from "@material-ui/core"; +import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction"; +import ListItemText from "@material-ui/core/ListItemText"; +import ListItemAvatar from "@material-ui/core/ListItemAvatar"; +import Checkbox from "@material-ui/core/Checkbox"; +import Avatar from "@material-ui/core/Avatar"; +import { Modal } from "./Modal"; +import { AvatarList } from "../Avatars/AvatarList"; +import { FilterBar } from "../FilterBar"; +import { User } from "../../types/User"; +import { patch } from "../../utils/http"; +import { Constants } from "../../utils/Constants"; + +interface IProps { + show: boolean; + users: User[]; + allUsers: User[]; + handleClose(): void; +} + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: "100%", + maxWidth: 360, + backgroundColor: theme.palette.background.paper, + }, + }) +); + +export const UsersModal: FC = ({ + show, + handleClose, + users, + allUsers, +}) => { + const { id } = useParams(); + + const [filterText, setFilterText] = useState(""); + const handleChange = (e: ChangeEvent): void => { + setFilterText(e.target.value); + }; + + const memberIDs = users.map((u) => u.id); + const [members, setMembers] = useState(memberIDs); + + const handleToggle = (value: string) => () => { + const currentIndex = members.indexOf(value); + const newChecked = [...members]; + + if (currentIndex === -1) { + newChecked.push(value); + } else { + newChecked.splice(currentIndex, 1); + } + + setMembers(newChecked); + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + await patch( + `${Constants.projectsURI}/${id}/members`, + members //.map((m) => m.id) + ); + handleClose(); + }; + + const classes = useStyles(); + + return ( + + + + setFilterText("")} + handleChange={handleChange} + /> + + + + {allUsers.map((u: User) => ( + + + + + + + + + + ))} + + + + ); +}; diff --git a/client/src/components/UsersModalEntry.tsx b/client/src/components/Modals/UsersModalEntry.tsx similarity index 84% rename from client/src/components/UsersModalEntry.tsx rename to client/src/components/Modals/UsersModalEntry.tsx index 36a67f4..7e7f04c 100644 --- a/client/src/components/UsersModalEntry.tsx +++ b/client/src/components/Modals/UsersModalEntry.tsx @@ -1,5 +1,5 @@ import React, { FC } from "react"; -import { User } from "../types/User"; +import { User } from "../../types/User"; interface IProps { setMembers: React.Dispatch>; @@ -9,7 +9,7 @@ interface IProps { export const UsersModalEntry: FC = ({ user, setMembers, members }) => { const match: (id: string) => boolean = (id: string) => { - return Boolean(members.find(m => m.id === id)); + return Boolean(members.find((m) => m.id === id)); }; return (
    @@ -22,7 +22,7 @@ export const UsersModalEntry: FC = ({ user, setMembers, members }) => { onChange={() => { !match(user.id) ? setMembers([...members, user]) - : setMembers(members.filter(p => p.id !== user.id)); + : setMembers(members.filter((p) => p.id !== user.id)); }} /> diff --git a/client/src/components/NewTicketForm.tsx b/client/src/components/NewTicketForm.tsx index 97e0108..aac5205 100644 --- a/client/src/components/NewTicketForm.tsx +++ b/client/src/components/NewTicketForm.tsx @@ -1,4 +1,5 @@ import React, { FC } from "react"; +import { TextField, MenuItem } from "@material-ui/core"; import { Project } from "../types/Project"; interface IProps { @@ -22,45 +23,17 @@ export const NewTicketForm: FC = ({ setEndingDate, allProjects, projectId, - setProjectId + setProjectId, }) => { return ( <> -
    -
    - note_add - ) => - setTitle(e.target.value) - } - /> - -
    - -
    - mode_edit - - -
    - + {/*
    date_range ) => setEndingDate(e.target.value) } @@ -81,14 +54,14 @@ export const NewTicketForm: FC = ({ - {allProjects.map(p => ( + {allProjects.map((p) => ( ))}
    -
    +
    */} ); }; diff --git a/client/src/components/NewTicketModal.tsx b/client/src/components/NewTicketModal.tsx deleted file mode 100644 index 055974f..0000000 --- a/client/src/components/NewTicketModal.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { FC, useState, FormEvent } from "react"; -import { useRouteMatch } from "react-router-dom"; -import { Modal } from "./Modal"; -import { NewTicketForm } from "./NewTicketForm"; -import { Ticket } from "../types/Ticket"; -import { Project } from "../types/Project"; -import { post } from "../utils/http"; -import { Constants } from "../utils/Constants"; -// import { HttpResponse } from "../types/HttpResponse"; - -interface IProps { - show: boolean; - handleClose(): void; - allProjects: Project[]; -} - -export const NewTicketModal: FC = ({ - show, - handleClose, - allProjects -}) => { - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [endingDate, setEndingDate] = useState(""); - - const { url } = useRouteMatch(); - const id = url.split("/")[2]; - const [projectId, setProjectId] = useState(id); - - const handleSubmit: (event: FormEvent) => void = async ( - e: FormEvent - ) => { - e.preventDefault(); - let newTicket = { - title: title, - description: description, - endingDate: new Date(endingDate).toISOString(), - creatorId: "20bf4b2a-7209-4826-96cd-29c2bc937a94", - projectId: parseInt(projectId) - }; - - // const response: HttpResponse = - await post(`${Constants.ticketsURI}`, newTicket); - handleClose(); - }; - - return ( - -
    -
    -

    New Ticket

    -
    - -
    - - close - -
    -
    - -
    -
    - -
    - -
    - -
    -
    -
    - ); -}; diff --git a/client/src/components/NewTicketTabRouter.tsx b/client/src/components/NewTicketTabRouter.tsx deleted file mode 100644 index 0b68000..0000000 --- a/client/src/components/NewTicketTabRouter.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { FC } from "react"; -import { useRouteMatch } from "react-router-dom"; -import { TabRouterHeader } from "./TabRouterHeader"; - -interface IProps { - tabNames: string[]; - description: string; - setDescription: React.Dispatch>; - title: string; - setTitle: React.Dispatch>; - endingDate: string; - setEndingDate: React.Dispatch>; -} - -export const NewTicketTabRouter: FC = ({ - tabNames, - description, - setDescription, - title, - setTitle, - endingDate, - setEndingDate -}) => { - const { url } = useRouteMatch(); - return ( - <> -
    - - - {/* */} -
    - - ); -}; diff --git a/client/src/components/Panels/ProjectTabPanel.tsx b/client/src/components/Panels/ProjectTabPanel.tsx new file mode 100644 index 0000000..b3fb77f --- /dev/null +++ b/client/src/components/Panels/ProjectTabPanel.tsx @@ -0,0 +1,118 @@ +import React, { FC, useState, ReactNode } from "react"; +import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"; +import AppBar from "@material-ui/core/AppBar"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import Typography from "@material-ui/core/Typography"; +import Box from "@material-ui/core/Box"; +import SwipeableViews from "react-swipeable-views"; +import { Ticket } from "../../types/Ticket"; +import { Project } from "../../types/Project"; +import { TicketList } from "../Lists/TicketList"; +// import { FileList } from "./AppFileList"; +import { AppFile } from "../../types/AppFile"; + +interface TabProps { + children?: ReactNode; + dir?: string; + index: any; + value: any; +} + +const TabPanel: FC = (props: TabProps) => { + const { children, value, index, ...other } = props; + + return ( + + ); +}; + +const a11yProps = (index: any) => { + return { + id: `full-width-tab-${index}`, + "aria-controls": `full-width-tabpanel-${index}`, + }; +}; + +const useStyles = makeStyles((theme: Theme) => ({ + root: { + backgroundColor: "#E9ECEF", + borderRadius: "20px", + }, +})); + +interface IProps { + tickets: Ticket[]; + remainingDays?: number; + tabNames: string[]; + files: AppFile[]; + // activities: Activity[]; + allProjects: Project[]; +} + +export const ProjectTabPanel: FC = ({ + tickets, + tabNames, + files, + allProjects, +}) => { + const classes = useStyles(); + const theme = useTheme(); + const [value, setValue] = useState(0); + + const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { + setValue(newValue); + }; + + const handleChangeIndex = (index: number) => { + setValue(index); + }; + + return ( +
    + + + {tabNames.map((t: string, i: number) => ( + + ))} + + + + + + + {/* + + */} + +
    + ); +}; diff --git a/client/src/components/TabRouter.tsx b/client/src/components/Panels/TabRouter.tsx similarity index 74% rename from client/src/components/TabRouter.tsx rename to client/src/components/Panels/TabRouter.tsx index 8ecfaba..24f9286 100644 --- a/client/src/components/TabRouter.tsx +++ b/client/src/components/Panels/TabRouter.tsx @@ -1,13 +1,12 @@ import React, { FC } from "react"; import { Route, useRouteMatch, Redirect } from "react-router-dom"; import { TabRouterHeader } from "./TabRouterHeader"; -import { TicketList } from "./TicketList"; -import { FileList } from "./AppFileList"; -// import { ActivityList } from "./ActivityList"; -import { Ticket } from "../types/Ticket"; -import { AppFile } from "../types/AppFile"; -import { Activity } from "../types/Activity"; -import { Project } from "../types/Project"; +import { TicketList } from "../Lists/TicketList"; +import { FileList } from "../Lists/AppFileList"; +import { Ticket } from "../../types/Ticket"; +import { AppFile } from "../../types/AppFile"; +import { Activity } from "../../types/Activity"; +import { Project } from "../../types/Project"; interface IProps { tickets: Ticket[]; @@ -23,7 +22,7 @@ export const TabRouter: FC = ({ tabNames, files, activities, - allProjects + allProjects, }) => { const { url } = useRouteMatch(); diff --git a/client/src/components/TabRouterHeader.tsx b/client/src/components/Panels/TabRouterHeader.tsx similarity index 100% rename from client/src/components/TabRouterHeader.tsx rename to client/src/components/Panels/TabRouterHeader.tsx diff --git a/client/src/components/Panels/UserTabPanel.tsx b/client/src/components/Panels/UserTabPanel.tsx new file mode 100644 index 0000000..ad0c2ba --- /dev/null +++ b/client/src/components/Panels/UserTabPanel.tsx @@ -0,0 +1,109 @@ +import React, { FC, useState, ReactNode } from "react"; +import SwipeableViews from "react-swipeable-views"; +import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"; +import AppBar from "@material-ui/core/AppBar"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import Typography from "@material-ui/core/Typography"; +import Box from "@material-ui/core/Box"; +import { Ticket } from "../../types/Ticket"; +import { Project } from "../../types/Project"; +import { ProjectList } from "../Lists/ProjectList"; +import { TicketList } from "../Lists/TicketList"; +import { User } from "../../types/User"; + +interface TabProps { + children?: ReactNode; + dir?: string; + index: any; + value: any; +} + +const TabPanel: FC = (props: TabProps) => { + const { children, value, index, ...other } = props; + + return ( + + ); +}; + +const a11yProps = (index: any) => { + return { + id: `full-width-tab-${index}`, + "aria-controls": `full-width-tabpanel-${index}`, + }; +}; + +const useStyles = makeStyles((theme: Theme) => ({ + root: { + backgroundColor: "#E9ECEF", + borderRadius: "20px", + }, + topbar: { borderTopLeftRadius: "10px", borderTopRightRadius: "10px" }, +})); + +interface IProps { + tabNames: string[]; + tickets: Ticket[]; + projects: Project[]; + allUsers: User[]; +} + +export const UserTabPanel: FC = ({ + tickets, + tabNames, + projects, + allUsers, +}) => { + const classes = useStyles(); + const theme = useTheme(); + const [value, setValue] = useState(0); + + const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { + setValue(newValue); + }; + + const handleChangeIndex = (index: number) => { + setValue(index); + }; + + return ( +
    + + + {tabNames.map((t: string, i: number) => ( + + ))} + + + + + + + + + + +
    + ); +}; diff --git a/client/src/components/UserTabRouter.tsx b/client/src/components/Panels/UserTabRouter.tsx similarity index 74% rename from client/src/components/UserTabRouter.tsx rename to client/src/components/Panels/UserTabRouter.tsx index af30bf3..6a45d4d 100644 --- a/client/src/components/UserTabRouter.tsx +++ b/client/src/components/Panels/UserTabRouter.tsx @@ -1,10 +1,10 @@ import React, { FC } from "react"; import { Route, useRouteMatch, Redirect } from "react-router-dom"; import { TabRouterHeader } from "./TabRouterHeader"; -import { ProjectList } from "./ProjectList"; -import { Ticket } from "../types/Ticket"; -import { Project } from "../types/Project"; -import { TicketList } from "./TicketList"; +import { ProjectList } from "../Lists/ProjectList"; +import { Ticket } from "../../types/Ticket"; +import { Project } from "../../types/Project"; +import { TicketList } from "../Lists/TicketList"; interface IProps { tabNames: string[]; @@ -23,7 +23,7 @@ export const UserTabRouter: FC = ({ tickets, tabNames, projects }) => { - + {/* */} diff --git a/client/src/components/ProfileSelector.tsx b/client/src/components/ProfileSelector.tsx index f689262..3c6fdb9 100644 --- a/client/src/components/ProfileSelector.tsx +++ b/client/src/components/ProfileSelector.tsx @@ -1,6 +1,5 @@ import React, { FC } from "react"; -import { HorizontalCard } from "./HorizontalCard"; -import { Avatar } from "./Avatar"; +import { UserAvatar } from "./Avatars/UserAvatar"; import { Link } from "react-router-dom"; export const ProfileSelector: FC = () => { @@ -10,7 +9,10 @@ export const ProfileSelector: FC = () => {

    Select a profile

    - +
    Demo User
    diff --git a/client/src/components/Progress/ProgressBar.tsx b/client/src/components/Progress/ProgressBar.tsx new file mode 100644 index 0000000..e3f837c --- /dev/null +++ b/client/src/components/Progress/ProgressBar.tsx @@ -0,0 +1,45 @@ +import React, { FC } from "react"; +import { makeStyles, Theme, createStyles } from "@material-ui/core/styles"; +import LinearProgress from "@material-ui/core/LinearProgress"; +import { Box } from "@material-ui/core"; + +type IProps = { + value: number; +}; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: "100%", + "& > * + *": { + marginTop: theme.spacing(2), + }, + }, + }) +); + +export const ProgressBar: FC = ({ value }) => { + // const styleString: CSSProperties = { width: `${value}%` }; + // let barColor: string = "green"; + + // if (value < 100) { + // barColor = "yellow"; + // } + // if (value < 200 / 3) { + // barColor = "orange"; + // } + // if (value < 100 / 3) { + // barColor = "red"; + // } + + const classes = useStyles(); + + return ( + +
    + + {/* */} +
    +
    + ); +}; diff --git a/client/src/components/Progress/ProgressInfo.tsx b/client/src/components/Progress/ProgressInfo.tsx new file mode 100644 index 0000000..eaaf028 --- /dev/null +++ b/client/src/components/Progress/ProgressInfo.tsx @@ -0,0 +1,41 @@ +import React, { FC } from "react"; +// import { makeStyles, Theme, createStyles } from "@material-ui/core/styles"; +import { Box } from "@material-ui/core"; +import { PlaylistAddCheck } from "@material-ui/icons"; + +type IProps = { + tasksTotalCount?: number; + tasksDone?: number; + remainingDays?: number; +}; + +// const useStyles = makeStyles((theme: Theme) => +// createStyles({ +// root: { +// width: "100%", +// "& > * + *": { +// marginTop: theme.spacing(2), +// }, +// }, +// }) +// ); + +export const ProgressInfo: FC = ({ + tasksDone, + tasksTotalCount, + remainingDays, +}) => { + // const classes = useStyles(); + + return ( + + + + {tasksDone}/{tasksTotalCount} + + + Due in {remainingDays} days + + + ); +}; diff --git a/client/src/components/ProgressBar.tsx b/client/src/components/ProgressBar.tsx deleted file mode 100644 index bf0ff90..0000000 --- a/client/src/components/ProgressBar.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { FC, CSSProperties } from "react"; - -type ProgressBarProps = { - value: number; - max?: number; - tasksTotalCount?: number; - tasksDone?: number; - remainingDays?: number; -}; - -export const ProgressBar: FC = ({ - value, - max = 100, - tasksDone, - tasksTotalCount, - remainingDays -}) => { - const styleString: CSSProperties = { width: `${value}%` }; - let barColor: string = "green"; - - if (value < 100) { - barColor = "yellow"; - } - if (value < 200 / 3) { - barColor = "orange"; - } - if (value < 100 / 3) { - barColor = "red"; - } - - return ( - <> -
    -
    -
    -
    -
    - playlist_add_check - - {tasksDone}/{tasksTotalCount} - -
    - Due {remainingDays} days -
    -
    -
    - - ); -}; diff --git a/client/src/components/ProjectList.tsx b/client/src/components/ProjectList.tsx deleted file mode 100644 index a574d41..0000000 --- a/client/src/components/ProjectList.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; -import { Ticket } from "../types/Ticket"; -import { HorizontalCard } from "./HorizontalCard"; -import { FilterBar } from "./FilterBar"; -import { put } from "../utils/http"; -import { Constants } from "../utils/Constants"; -import { HttpResponse } from "../types/HttpResponse"; -import { Project } from "../types/Project"; - -type IProps = { - projects: Project[]; -}; - -export const ProjectList: FC = ({ projects }) => { - const [filterText, setFilterText] = useState(""); - const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { - setFilterText(""); - }; - - const handleChange: (e: ChangeEvent) => void = ( - e: ChangeEvent - ) => { - setFilterText(e.target.value); - }; - - let filteredTickets = projects.filter( - t => - t.status !== "Done" && - t.title.toLowerCase().includes(filterText.toLowerCase()) - ); - return ( - <> -
    -

    Projects

    - -
    -
    -
      - {filteredTickets.length === 0 ? ( - - ) : ( - filteredTickets.map((t: Project) => ( - { - e.preventDefault(); - await put>( - `${Constants.ticketsURI}/${t.id}/closed`, - {} - ); - }} - /> - )) - )} -
    -
    - - ); -}; diff --git a/client/src/components/SignInSide.tsx b/client/src/components/SignInSide.tsx new file mode 100644 index 0000000..bb58e38 --- /dev/null +++ b/client/src/components/SignInSide.tsx @@ -0,0 +1,118 @@ +import React from "react"; +import Avatar from "@material-ui/core/Avatar"; +import Button from "@material-ui/core/Button"; +import CssBaseline from "@material-ui/core/CssBaseline"; +import TextField from "@material-ui/core/TextField"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Checkbox from "@material-ui/core/Checkbox"; +import Link from "@material-ui/core/Link"; +import Paper from "@material-ui/core/Paper"; +import Grid from "@material-ui/core/Grid"; +import LockOutlinedIcon from "@material-ui/icons/LockOutlined"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles, createStyles, Theme } from "@material-ui/core/styles"; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + height: "100vh" + }, + image: { + backgroundImage: "url(https://source.unsplash.com/daily?dev)", + backgroundRepeat: "no-repeat", + backgroundColor: + theme.palette.type === "light" + ? theme.palette.grey[50] + : theme.palette.grey[900], + backgroundSize: "cover", + backgroundPosition: "center" + }, + paper: { + margin: theme.spacing(8, 4), + display: "flex", + flexDirection: "column", + alignItems: "center" + }, + avatar: { + margin: theme.spacing(1), + backgroundColor: theme.palette.secondary.main + }, + form: { + width: "100%", + marginTop: theme.spacing(1) + }, + submit: { + margin: theme.spacing(3, 0, 2) + } + }) +); + +export default function SignInSide() { + const classes = useStyles(); + + return ( + + + + +
    + + + + + Sign in + +
    + + + } + label="Remember me" + /> + + + + + Forgot password? + + + + + {"Don't have an account? Sign Up"} + + + + +
    +
    +
    + ); +} diff --git a/client/src/components/TicketList.tsx b/client/src/components/TicketList.tsx deleted file mode 100644 index 149eabe..0000000 --- a/client/src/components/TicketList.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; -import { Ticket } from "../types/Ticket"; -import { FloatingButton } from "./FloatingButton"; -import { HorizontalCard } from "./HorizontalCard"; -import { FilterBar } from "./FilterBar"; -import { HttpResponse } from "../types/HttpResponse"; -import { put } from "../utils/http"; -import { Constants } from "../utils/Constants"; -import { NewTicketModal } from "./NewTicketModal"; -import { Project } from "../types/Project"; - -type TicketListProps = { - tickets: Ticket[]; - allProjects: Project[]; - addButton?: Boolean; -}; - -export const TicketList: FC = ({ - tickets, - allProjects, - addButton = true -}) => { - const [filterText, setFilterText] = useState(""); - const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { - setFilterText(""); - }; - - const onClick: (e: MouseEvent) => void = (e: MouseEvent) => { - e.preventDefault(); - setShowNew(true); - }; - const handleChange: (e: ChangeEvent) => void = ( - e: ChangeEvent - ) => { - setFilterText(e.target.value); - }; - - const [showNew, setShowNew] = useState(false); - let filteredTickets = tickets.filter( - t => - t.status !== "Done" && - t.title.toLowerCase().includes(filterText.toLowerCase()) - ); - return ( - <> -
    - { - setShowNew(false); - }} - show={showNew} - allProjects={allProjects} - /> -

    Tickets

    - {addButton && ( - - )} - -
    -
    -
      - {filteredTickets.length === 0 ? ( - - ) : ( - filteredTickets.map((t: Ticket) => ( - { - e.preventDefault(); - await put>( - `${Constants.ticketsURI}/${t.id}/closed`, - {} - ); - }} - /> - )) - )} -
    -
    - - ); -}; diff --git a/client/src/components/UserHeader.tsx b/client/src/components/UserHeader.tsx index 3242e63..98f6ff9 100644 --- a/client/src/components/UserHeader.tsx +++ b/client/src/components/UserHeader.tsx @@ -1,21 +1,36 @@ import React, { FC } from "react"; import { Header } from "../components/Header"; -import { Avatar } from "../components/Avatar"; +import { UserAvatar } from "./Avatars/UserAvatar"; +import { + Grid, + // makeStyles, Theme +} from "@material-ui/core"; interface IProps { fullName: string; presentation: string; picture: string; } + +// const useStyles = makeStyles((theme: Theme) => ({ +// root: { +// paddingTop: theme.spacing(2), +// flexGrow: 1, +// }, +// })); + export const UserHeader: FC = ({ fullName, presentation, picture }) => { + // const classes = useStyles(); return ( -
    -
    - -
    -
    + //
    + + + + +
    -
    -
    + + + //
    ); }; diff --git a/client/src/controllers/UserController.tsx b/client/src/controllers/UserController.tsx index 181a3fa..fc31881 100644 --- a/client/src/controllers/UserController.tsx +++ b/client/src/controllers/UserController.tsx @@ -14,6 +14,7 @@ export const UserController: FC = () => { const [user, setUser] = useState({} as User); const [hasError, setHasError] = useState(false); const [error, setError] = useState(""); + const [allUsers, setAllUsers] = useState([]); const { id } = useParams(); async function httpGetUser(id: string): Promise { @@ -32,9 +33,24 @@ export const UserController: FC = () => { } } + async function httpGetAllUsers(): Promise { + try { + const response: HttpResponse = await get( + `${Constants.usersURI}` + ); + if (response.parsedBody !== undefined) { + setAllUsers((response.parsedBody as unknown) as User[]); + } + } catch (ex) { + setHasError(true); + setError(ex); + } + } + useEffect(() => { if (id !== undefined) { httpGetUser(id); + httpGetAllUsers(); } else { setHasError(true); setError("Bad Request"); @@ -45,6 +61,6 @@ export const UserController: FC = () => { return ; } - const viewModel = new UserVM(user); + const viewModel = new UserVM(user, allUsers); return isLoading ? : ; }; diff --git a/client/src/layouts/MainLayout.tsx b/client/src/layouts/MainLayout.tsx new file mode 100644 index 0000000..9bc6507 --- /dev/null +++ b/client/src/layouts/MainLayout.tsx @@ -0,0 +1,41 @@ +import React, { FC } from "react"; +import CssBaseline from "@material-ui/core/CssBaseline"; +import { makeStyles } from "@material-ui/core/styles"; +import ButtonAppBar from "../components/ButtonAppBar"; +import Footer from "../components/Footer"; + +/** + * @function useStyles creates the css styles used in the following component. + */ +const useStyles = makeStyles((theme) => ({ + // root style allow for fixed footer + root: { + display: "flex", + flexDirection: "column", + minHeight: "100vh", + backgroundColor: "#f8f9fa", + }, +})); + +/** + * MainLayout is the principal page layout. It mainly ensure the footer is fixed + * to the page bottom. + * + * @param children - The encapsulated component. + */ +const MainLayout: FC = ({ children }) => { + const classes = useStyles(); + return ( +
    +
    + +
    + {/* */} + + {children} +
    +
    + ); +}; + +export default MainLayout; diff --git a/client/src/layouts/PageLayout.tsx b/client/src/layouts/PageLayout.tsx new file mode 100644 index 0000000..3c1c099 --- /dev/null +++ b/client/src/layouts/PageLayout.tsx @@ -0,0 +1,43 @@ +import React, { FC, ReactNode } from "react"; +import { makeStyles, Theme, Container } from "@material-ui/core"; + +/** + * @function useStyles creates the css styles used in the following component. + */ +const useStyles = makeStyles((theme: Theme) => ({ + // root style allow for fixed footer + header: { + margin: theme.spacing(1), + paddingTop: theme.spacing(2), + paddingBottom: theme.spacing(2), + flexGrow: 1, + }, + content: { + margin: theme.spacing(1), + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + }, +})); + +interface IProps { + header: ReactNode; + content: ReactNode; +} + +/** + * PageLayout divide the page in 2 parts: Header and Content, to ensure cohesion. + * + * @param Header - The encapsulated component. + * @param Content - The encapsulated component. + */ +const PageLayout: FC = ({ header, content }) => { + const classes = useStyles(); + return ( + +
    {header}
    +
    {content}
    +
    + ); +}; + +export default PageLayout; diff --git a/client/src/pages/HomePage.tsx b/client/src/pages/HomePage.tsx index d6676c9..e6fe164 100644 --- a/client/src/pages/HomePage.tsx +++ b/client/src/pages/HomePage.tsx @@ -1,21 +1,23 @@ import React from "react"; -import { LogInForm } from "../components/LogInForm"; -import { ProfileSelector } from "../components/ProfileSelector"; +// import { LogInForm } from "../components/LogInForm"; +// import { ProfileSelector } from "../components/ProfileSelector"; +import SignInSide from "../components/SignInSide"; export const HomePage: React.FC = () => { return ( -
    -
    -

    Ticket Manager

    -
    -
    - -
    -
    - -
    -
    -
    -
    + //
    + //
    + //

    Ticket Manager

    + //
    + //
    + // + //
    + //
    + // + //
    + //
    + //
    + //
    + ); }; diff --git a/client/src/pages/Layout.tsx b/client/src/pages/Layout.tsx deleted file mode 100644 index cf9a792..0000000 --- a/client/src/pages/Layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { FC } from "react"; -import { AppRouter } from "../utils/router"; -import { NavBar } from "../components/Navbar"; - -const Layout: FC = () => { - return ( - <> -
    - -
    - {/* */} - - {/*