From b48bdf70b1f6ce2ab81e9b9c23b06c10aae299d2 Mon Sep 17 00:00:00 2001 From: Philipp Berger <philipp.berger@nexenio.com> Date: Thu, 5 Aug 2021 13:22:48 +0200 Subject: [PATCH] chore: release v1.7.0 --- CHANGELOG.md | 12 + e2e/package.json | 2 +- .../groupSearch/searchForGroupByName.spec.js | 55 +++ .../groupSearch/searchForGroupByZip.spec.js | 73 +++ e2e/specs/health-department/helper/routes.js | 2 + .../helper/ui/login.helper.js | 6 +- .../helper/ui/tracking.helper.js | 17 + .../locationTracking/closeProcess.spec.js | 36 ++ .../userManagement/addEmployee.spec.js | 53 +++ .../cameraScannerCheckin.spec.js | 5 +- .../hardwareScannerCheckin.spec.js | 1 - .../locations/downloadPDF/downloadPDF.spec.js | 30 +- e2e/specs/locations/helpers/functions.js | 6 + .../helpers/inputValidation.helper.js | 25 + .../locationOverview/locationOverview.spec.js | 76 ++-- .../webapp/checkin/checkinByLinkFlow.spec.js | 86 ++-- e2e/specs/webapp/helpers/routes.js | 1 + .../workflow/senarios/contactForm.spec.js | 52 +-- package.json | 2 +- services/backend/package.json | 3 +- services/backend/src/app.js | 2 + ...0-1310-addIndexOnDeviceTypeToUsersTable.js | 13 + services/backend/src/database/models/index.js | 23 + .../backend/src/middlewares/requestMetrics.js | 51 +++ .../src/routes/v3/locationTransfers.js | 1 + .../backend/src/routes/v3/tests.openapi.yaml | 1 + .../routes/v3/tracingProcesses.openapi.yaml | 2 + services/backend/src/utils/validation.test.js | 7 +- services/backend/src/utils/validation.ts | 2 +- services/backend/yarn.lock | 264 ++++++----- services/contact-form/.eslintrc.js | 1 + services/contact-form/.iyarc | 12 + services/contact-form/package.json | 2 +- .../AddressInput/AddressInput.react.js | 17 +- .../components/InputForm/InputForm.react.js | 11 +- .../src/components/hooks/useValidators.js | 111 +++-- .../src/constants/errorMessages.js | 27 -- .../contact-form/src/constants/valueLength.js | 2 +- services/contact-form/src/messages/de.json | 3 - services/contact-form/src/messages/en.json | 3 - .../contact-form/src/utils/checkCharacter.js | 10 +- .../src/utils/validatorRules.helper.js | 8 +- .../contact-form/src/utils/validatorRules.js | 28 +- services/health-department/.iyarc | 12 + services/health-department/package.json | 2 +- services/health-department/src/ant.css | 44 +- .../src/components/App/App.react.js | 10 +- .../Header/LogoutButton/LogoutButton.react.js | 7 +- .../LogoutButton/LogoutButton.styled.js | 11 - .../ToggleCompleted/ToggleCompleted.react.js | 7 +- .../ToggleCompleted/ToggleCompleted.styled.js | 10 - .../ContactConfirmationButton.react.js | 21 +- .../ContactConfirmationButton.styled.js | 26 -- .../ChangePasswordView.react.js | 13 +- .../ChangePasswordView.styled.js | 11 - .../DownloadSigningTool.react.js | 11 +- .../DownloadSigningTool.styled.js | 11 - .../ManualSearchButton.react.js | 10 +- .../NewTrackingButton.react.js | 7 +- .../App/Tracking/Tracking.styled.js | 7 - .../EmptyProcesses/EmptyProcesses.react.js | 2 +- .../TrackingList/Table/Entry/Entry.styled.js | 7 - .../AddEmployeeButton.react.js | 18 +- .../EmployeeActions/EmployeeActions.react.js | 21 +- .../EmployeeActions/EmployeeActions.styled.js | 19 - .../AddEmployeeForm/AddEmployeeForm.react.js | 19 +- .../AddEmployeeModal.styled.js | 21 - .../ConfirmPassword/ConfirmPassword.react.js | 17 +- .../DataRequestModal.react.js | 11 +- .../DataRequestModal.styled.js | 20 - .../GroupSearchModal.react.js | 40 +- .../GroupSearchModal.styled.js | 46 +- .../EmptySearch/EmptySearch.react.js | 0 .../{ => SearchResults}/EmptySearch/index.js | 0 .../SearchResults/SearchResults.react.js | 40 ++ .../SearchResults/SearchResults.styled.js | 35 ++ .../GroupSearchModal/SearchResults/index.js | 1 + .../CompleteStep/CompleteStep.react.js | 14 +- .../GenerateKeysStep.react.js | 18 +- .../ConfirmNewPassword.react.js | 8 +- .../CreateNewPassword.react.js | 19 +- .../RenewEmployeePasswordModal.styled.js | 21 - .../CredentialsStep/CredentialsStep.react.js | 7 +- .../SelectCaseStep/SelectCaseStep.react.js | 7 +- .../TrackInfectionModal.react.js | 8 +- .../TrackInfectionModal.styled.js | 8 - .../UploadKeyFileModal.react.js | 36 +- .../UploadKeyFileModal.styled.js | 7 - .../src/components/Login/Login.react.js | 12 +- .../src/components/general/Buttons.styled.js | 86 ++++ .../src/components/general/index.js | 5 + .../src/utils/sanitizer.test.js | 13 + services/locations/.iyarc | 12 + services/locations/package.json | 2 +- .../locations/src/assets/verification.svg | 12 + services/locations/src/assets/warning.svg | 13 + .../LocationFooter/LocationFooter.react.js | 2 +- .../GuestListModal/GuestListModal.react.js | 16 +- .../components/ShareData/ShareData.styled.js | 9 +- .../ShareDataStep/Checkins/Checkins.react.js | 20 + .../ShareData/ShareDataStep/Checkins/index.js | 1 + .../DataRequests/DataRequests.react.js | 54 +++ .../RequestWarning/RequestWarning.react.js | 25 + .../RequestWarning/RequestWarning.styled.js | 6 + .../DataRequests/RequestWarning/index.js | 1 + .../ShareDataStep/DataRequests/index.js | 1 + .../HealthDepartmentInfo.react.js | 41 ++ .../VerificationTag/VerificationTag.react.js | 27 ++ .../VerificationTag/VerificationTag.styled.js | 6 + .../VerificationTag/index.js | 1 + .../HealthDepartmentInfo/index.js | 1 + .../ShareDataStep/ShareDataStep.react.js | 70 +-- .../ShareDataStep/ShareDataStep.styled.js | 30 ++ services/locations/src/messages/de.json | 8 +- services/locations/src/messages/en.json | 8 +- .../locations/src/utils/checkCharacter.js | 2 +- services/locations/src/utils/downloadPDF.js | 12 +- .../src/workers/createQRCodePDF.worker.js | 68 ++- services/scanner/.iyarc | 12 + services/scanner/package.json | 2 +- .../components/CamScanner/CamScanner.react.js | 1 + services/scanner/src/constants/timeouts.js | 2 +- services/webapp/.iyarc | 12 + services/webapp/package.json | 2 +- services/webapp/src/App.react.js | 74 +-- .../components/AuthenticationWrapper.react.js | 14 +- .../src/components/Checkout/Checkout.react.js | 164 +++---- .../components/Checkout/Checkout.styled.js | 64 +-- .../CheckoutButton/CheckoutButton.react.js | 18 + .../CheckoutButton/CheckoutButton.styled.js | 23 + .../Checkout/CheckoutButton/index.js | 1 + .../LocationInfoContainer.react.js | 30 ++ .../LocationInfoContainer.styled.js | 22 + .../Checkout/LocationInfoContainer/index.js | 1 + .../TimerDisplay/TimerDisplay.react.js | 25 + .../TimerDisplay/TimerDisplay.styled.js | 19 + .../components/Checkout/TimerDisplay/index.js | 1 + .../webapp/src/components/Home/Home.react.js | 12 +- .../Home/SelfCheckin/SelfCheckin.react.js | 9 +- .../src/components/Home/useCheckInCheck.js | 2 +- .../src/components/Home/useTraceQRCode.js | 9 +- .../webapp/src/contexts/userSessionContext.js | 34 ++ services/webapp/src/helper.js | 1 + services/webapp/src/hooks/useLocalStorage.js | 12 + services/webapp/src/hooks/useTraceClock.js | 26 ++ services/webapp/yarn.lock | 426 +++++++++--------- sonar-project.properties | 2 +- 147 files changed, 2042 insertions(+), 1376 deletions(-) create mode 100644 e2e/specs/health-department/groupSearch/searchForGroupByName.spec.js create mode 100644 e2e/specs/health-department/groupSearch/searchForGroupByZip.spec.js create mode 100644 e2e/specs/health-department/helper/ui/tracking.helper.js create mode 100644 e2e/specs/health-department/tracking/locationTracking/closeProcess.spec.js create mode 100644 e2e/specs/health-department/userManagement/addEmployee.spec.js create mode 100644 services/backend/src/database/migrations/2021.07.20-1310-addIndexOnDeviceTypeToUsersTable.js create mode 100644 services/backend/src/middlewares/requestMetrics.js delete mode 100644 services/contact-form/src/constants/errorMessages.js delete mode 100644 services/health-department/src/components/App/Header/LogoutButton/LogoutButton.styled.js delete mode 100644 services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.styled.js delete mode 100644 services/health-department/src/components/App/Tracking/TrackingList/Table/Entry/Entry.styled.js rename services/health-department/src/components/App/modals/GroupSearchModal/{ => SearchResults}/EmptySearch/EmptySearch.react.js (100%) rename services/health-department/src/components/App/modals/GroupSearchModal/{ => SearchResults}/EmptySearch/index.js (100%) create mode 100644 services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.react.js create mode 100644 services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.styled.js create mode 100644 services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/index.js create mode 100644 services/health-department/src/components/general/Buttons.styled.js create mode 100644 services/health-department/src/components/general/index.js create mode 100644 services/locations/src/assets/verification.svg create mode 100644 services/locations/src/assets/warning.svg create mode 100644 services/locations/src/components/ShareData/ShareDataStep/Checkins/Checkins.react.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/Checkins/index.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/DataRequests/DataRequests.react.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.react.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.styled.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/index.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/DataRequests/index.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/HealthDepartmentInfo.react.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.react.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.styled.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/index.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/index.js create mode 100644 services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.styled.js create mode 100644 services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.react.js create mode 100644 services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.styled.js create mode 100644 services/webapp/src/components/Checkout/CheckoutButton/index.js create mode 100644 services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.react.js create mode 100644 services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.styled.js create mode 100644 services/webapp/src/components/Checkout/LocationInfoContainer/index.js create mode 100644 services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.react.js create mode 100644 services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.styled.js create mode 100644 services/webapp/src/components/Checkout/TimerDisplay/index.js create mode 100644 services/webapp/src/contexts/userSessionContext.js create mode 100644 services/webapp/src/hooks/useLocalStorage.js create mode 100644 services/webapp/src/hooks/useTraceClock.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b8060..2642c5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +### 1.7.0 (2021-08-05) +* **backend:** feat: instrument with metrics +* **health-department:** feat: update route access restrictions for employees +* **health-department:** feat: unified buttons and updated designs +* **locations:** fix: qr code labels and names +* **locations:** feat: inform venue owners about unusally long request times from health departments before sharing data +* **locations:** feat: support special characters in location creation +* **contact-form:** feat: updated string validation +* **webapp:** fix: checkin timer starting time +* **webapp:** fix: timer resets after refresh +* **scanner:** fix: improve camera resolution to scan badges faster + ### 1.6.2 (2021-07-29) * **backend:** ref: split checkin route for scanner and contact-form diff --git a/e2e/package.json b/e2e/package.json index 149b2fa..1d764d7 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "1.6.2", + "version": "1.7.0", "main": "index.js", "private": true, "engines": { diff --git a/e2e/specs/health-department/groupSearch/searchForGroupByName.spec.js b/e2e/specs/health-department/groupSearch/searchForGroupByName.spec.js new file mode 100644 index 0000000..2f52395 --- /dev/null +++ b/e2e/specs/health-department/groupSearch/searchForGroupByName.spec.js @@ -0,0 +1,55 @@ +import { login, logout, createGroup, deleteGroup } from '../../locations/helpers/functions'; +import { loginHealthDepartment } from '../helper/api/auth.helper'; +import { addHealthDepartmentPrivateKeyFile } from '../helper/ui/login.helper'; +import { createGroupPayload } from '../../locations/helpers/functions.helper'; + +const FIRST_GROUP_NAME = 'test group first'; +const SECOND_GROUP_NAME = 'second test@group'; +const THIRD_GROUP_NAME = 'not matching group'; +const GROUPS = [FIRST_GROUP_NAME, SECOND_GROUP_NAME, THIRD_GROUP_NAME]; +const MATCHED_GROUPS = [FIRST_GROUP_NAME, SECOND_GROUP_NAME]; +const GROUP_IDS = []; +const GROUP_PREFIX = 'group_'; +const SEARCH_TERM = 'tes'; + + +describe('Health Department / Group search / Search for a group', () => { + //create groups with different names + before(() => { + login(); + GROUPS.forEach(group => { + createGroup({ ...createGroupPayload, name: group }); + cy.get('@groupId').then(groupId => { + GROUP_IDS.push(groupId); + }); + }); + logout(); + }); + //delete created groups + after(() => { + login(); + GROUP_IDS.forEach(id => deleteGroup(id)); + }); + + describe('when searching for groups by name', () => { + it('display groups matching search term', () => { + loginHealthDepartment(); + addHealthDepartmentPrivateKeyFile(); + cy.getByCy('searchGroup').should('exist').should('be.visible').click(); + //search group by term + cy.get('.ant-modal-content').within(($modal) => { + cy.getByCy('groupNameInput').should('exist').should('be.visible').type(SEARCH_TERM); + cy.getByCy('startGroupSearch').should('exist').should('be.visible').click(); + //verify 2 groups are found and one is noot + MATCHED_GROUPS.forEach((group) => { + cy.getByCy(GROUP_PREFIX + group).should('exist').should('be.visible'); + cy.getByCy(GROUP_PREFIX + group).children().then(($group) => { + expect($group.text()).contains(group); + expect($group.text()).contains(createGroupPayload.streetName + ' ' + createGroupPayload.streetNr + ', ' + createGroupPayload.zipCode + ' ' + createGroupPayload.city); + }); + }); + cy.getByCy(GROUP_PREFIX + THIRD_GROUP_NAME).should('not.exist'); + }); + }); + }); +}); diff --git a/e2e/specs/health-department/groupSearch/searchForGroupByZip.spec.js b/e2e/specs/health-department/groupSearch/searchForGroupByZip.spec.js new file mode 100644 index 0000000..134160c --- /dev/null +++ b/e2e/specs/health-department/groupSearch/searchForGroupByZip.spec.js @@ -0,0 +1,73 @@ +import { login, logout, createGroup, deleteGroup } from '../../locations/helpers/functions'; +import { loginHealthDepartment } from '../helper/api/auth.helper'; +import { addHealthDepartmentPrivateKeyFile } from '../helper/ui/login.helper'; +import { createGroupPayload } from '../../locations/helpers/functions.helper'; + +const FIRST_GROUP_NAME = 'test group first'; +const SECOND_GROUP_NAME = 'test group second'; +const THIRD_GROUP_NAME = 'not matching group'; +const ZIP_CODE_MATCHED = '12345'; +const ZIP_CODE_MISMATCHED = '54321'; +const GROUP_ZIP_MAP = { [FIRST_GROUP_NAME]: ZIP_CODE_MATCHED, [SECOND_GROUP_NAME]: ZIP_CODE_MISMATCHED, [THIRD_GROUP_NAME]: ZIP_CODE_MATCHED }; +const GROUP_IDS = []; +const GROUP_PREFIX = 'group_'; +const SEARCH_TERM = 'tes'; + + +describe('Health Department / Group search / Search for a group by Name and Zip code', () => { + //create groups with different names and zip codes + before(() => { + login(); + for (const [key, value] of Object.entries(GROUP_ZIP_MAP)) { + createGroup({ ...createGroupPayload, name: key, zipCode: value }); + cy.get('@groupId').then(groupId => { + GROUP_IDS.push(groupId); + }); + }; + logout(); + }); + + beforeEach(() => { + loginHealthDepartment(); + addHealthDepartmentPrivateKeyFile(); + }); + + //delete created groups + after(() => { + login(); + GROUP_IDS.forEach(id => deleteGroup(id)); + }); + + describe('when searching for groups by zip code', () => { + it('disables the search button', () => { + cy.getByCy('searchGroup').should('exist').should('be.visible').click(); + //search group by term + cy.get('.ant-modal-content').within(($modal) => { + cy.get('#zipCode').should('exist').should('be.visible').type(ZIP_CODE_MATCHED); + cy.getByCy('startGroupSearch').should('exist').should('be.disabled'); + }); + }); + }); + + describe('when searching for a groups by Name and Zip code', () => { + it('displays groups matching search criteria', () => { + cy.getByCy('searchGroup').should('exist').should('be.visible').click(); + //search groups by name and zip + cy.get('.ant-modal-content').within(($modal) => { + cy.getByCy('groupNameInput').should('exist').should('be.visible').type(SEARCH_TERM); + cy.get('#zipCode').should('exist').should('be.visible').type(ZIP_CODE_MATCHED); + cy.getByCy('startGroupSearch').should('exist').should('be.visible').click(); + //verify one group matching name and zip is found + cy.getByCy(GROUP_PREFIX + FIRST_GROUP_NAME).should('exist').should('be.visible'); + cy.getByCy(GROUP_PREFIX + FIRST_GROUP_NAME).children().then(($group) => { + expect($group.text()).contains(FIRST_GROUP_NAME); + expect($group.text()).contains(`${createGroupPayload.streetName} ${createGroupPayload.streetNr}, ${ZIP_CODE_MATCHED} ${createGroupPayload.city}`); + }); + //verify groups with different name or zip are not found + cy.getByCy(GROUP_PREFIX + SECOND_GROUP_NAME).should('not.exist'); + cy.getByCy(GROUP_PREFIX + THIRD_GROUP_NAME).should('not.exist'); + }); + }); + }); + +}); \ No newline at end of file diff --git a/e2e/specs/health-department/helper/routes.js b/e2e/specs/health-department/helper/routes.js index 1d6b426..dd9151d 100644 --- a/e2e/specs/health-department/helper/routes.js +++ b/e2e/specs/health-department/helper/routes.js @@ -1,2 +1,4 @@ export const HEALTH_DEPARTMENT_BASE_ROUTE = '/health-department'; export const HEALTH_DEPARTMENT_APP_ROUTE = `${HEALTH_DEPARTMENT_BASE_ROUTE}/app`; +export const HEALTH_DEPARTMENT_TRACKING_ROUTE = '/app/tracking'; +export const HEALTH_DEPARTMENT_USER_MANAGEMENT_ROUTE = `${HEALTH_DEPARTMENT_BASE_ROUTE}/app/user-management`; diff --git a/e2e/specs/health-department/helper/ui/login.helper.js b/e2e/specs/health-department/helper/ui/login.helper.js index fa3ad47..ebf7eba 100644 --- a/e2e/specs/health-department/helper/ui/login.helper.js +++ b/e2e/specs/health-department/helper/ui/login.helper.js @@ -41,11 +41,7 @@ export const uploadHealthDepartmentPrivateKeyFile = () => { }; export const removeHealthDepartmentPrivateKeyFile = () => { - cy.task('fileExists', HEALTH_DEPARTMENT_PRIVATE_KEY_PATH).then(exists => { - if (exists) { - cy.exec(`rm ${HEALTH_DEPARTMENT_PRIVATE_KEY_PATH}`); - } - }); + cy.task('deleteFileIfExists', HEALTH_DEPARTMENT_PRIVATE_KEY_PATH); }; export const addHealthDepartmentPrivateKeyFile = () => { diff --git a/e2e/specs/health-department/helper/ui/tracking.helper.js b/e2e/specs/health-department/helper/ui/tracking.helper.js new file mode 100644 index 0000000..5cf74a2 --- /dev/null +++ b/e2e/specs/health-department/helper/ui/tracking.helper.js @@ -0,0 +1,17 @@ +export const setDatePickerTime = () => { + cy.get('.ant-picker-dropdown').not('.ant-picker-dropdown-hidden').should('exist').within(() => { + cy.get('.ant-picker-time-panel-cell').eq(0).click().type('{enter}'); + }); +}; + +export const setDatePickerStartDate = (startDate) => { + cy.get('#startDate').should('exist').should('be.visible').click().type(`${startDate}{enter}`); + cy.get('#startTime').should('exist').should('be.visible').click(); + setDatePickerTime(); +}; + +export const setDatePickerEndDate = (endDate) => { + cy.get('#endDate').should('exist').click({ force: true }).type(`${endDate}{enter}`); + cy.get('#endTime').should('exist').click(); + setDatePickerTime(); +}; \ No newline at end of file diff --git a/e2e/specs/health-department/tracking/locationTracking/closeProcess.spec.js b/e2e/specs/health-department/tracking/locationTracking/closeProcess.spec.js new file mode 100644 index 0000000..4b30e8d --- /dev/null +++ b/e2e/specs/health-department/tracking/locationTracking/closeProcess.spec.js @@ -0,0 +1,36 @@ +import moment from 'moment'; +import { loginHealthDepartment } from '../../helper/api/auth.helper'; +import { addHealthDepartmentPrivateKeyFile } from '../../helper/ui/login.helper'; +import { setDatePickerStartDate, setDatePickerEndDate } from '../../helper/ui/tracking.helper'; +import { E2E_DEFAULT_GROUP_NAME } from '../../../locations/helpers/locations.js'; + +const GROUP_PREFIX = 'group_'; +const YESTERDAY = moment().subtract(1, 'days').format('DD.MM.YYYY'); +const TODAY = moment().format('DD.MM.YYYY'); + +describe('Health Department / Tracking / Location tracking', () => { + describe('when close tracking process', () => { + it('the process disappears from the list', () => { + loginHealthDepartment(); + addHealthDepartmentPrivateKeyFile(); + //search E2E group + cy.getByCy('searchGroup').should('exist').should('be.visible').click(); + cy.getByCy('groupNameInput').should('exist').should('be.visible').type(E2E_DEFAULT_GROUP_NAME); + cy.getByCy('startGroupSearch').should('exist').should('be.visible').click(); + cy.getByCy(GROUP_PREFIX + E2E_DEFAULT_GROUP_NAME).should('exist').should('be.visible').click(); + //set tracking dates + setDatePickerStartDate(YESTERDAY); + setDatePickerEndDate(TODAY); + //open tracking process + cy.getByCy('requestGroupData').click(); + cy.getByCy('processEntry').should('contain', E2E_DEFAULT_GROUP_NAME).first().click(); + //close tracking process + cy.getByCy('complete').should('exist').click(); + cy.get('.ant-popover-inner-content').within(($popup) => { + cy.get('.ant-btn-primary').should('exist').click(); + }); + //verify process is not in the list + cy.getByCy('emptyProcesses').should('exist').should('be.visible'); + }); + }); +}); diff --git a/e2e/specs/health-department/userManagement/addEmployee.spec.js b/e2e/specs/health-department/userManagement/addEmployee.spec.js new file mode 100644 index 0000000..d346dfc --- /dev/null +++ b/e2e/specs/health-department/userManagement/addEmployee.spec.js @@ -0,0 +1,53 @@ +import faker from 'faker'; +import { loginHealthDepartment, logout } from '../helper/api/auth.helper'; +import { loginToHD, openHDLoginPage, addHealthDepartmentPrivateKeyFile } from '../helper/ui/login.helper'; +import { HEALTH_DEPARTMENT_TRACKING_ROUTE, HEALTH_DEPARTMENT_USER_MANAGEMENT_ROUTE } from '../helper/routes'; + +const EMPLOYEE_FIRST_NAME = faker.name.firstName(); +const EMPLOYEE_LAST_NAME = faker.name.lastName(); +const EMPLOYEE_PHONE = faker.phone.phoneNumber('170 #######'); +const EMPLOYEE_EMAIL = 'employee@hd.com'; + +describe('Health Department / User Management / Create new employee', () => { + describe('when create a new HD employee', () => { + it('the employee is created and able to login', () => { + loginHealthDepartment(); + cy.visit(HEALTH_DEPARTMENT_USER_MANAGEMENT_ROUTE); + cy.getByCy('addEmployee').should('exist').should('be.visible').click(); + //fillin employee form + cy.get('.ant-modal').within(($modal) => { + cy.get('#firstName').should('exist').should('be.visible').type(EMPLOYEE_FIRST_NAME); + cy.get('#lastName').should('exist').should('be.visible').type(EMPLOYEE_LAST_NAME); + cy.get('#phone').should('exist').should('be.visible').type(EMPLOYEE_PHONE); + cy.get('#email').should('exist').should('be.visible').type(EMPLOYEE_EMAIL); + cy.get('button[type=button]').should('exist').should('be.visible'); + cy.get('button[type=submit]').should('exist').should('be.visible').click(); + }); + //save generated password + cy.get('.ant-notification').should('exist').should('be.visible'); + cy.get('.ant-modal').within(($modal) => { + cy.getByCy('generatedPassword').then(($password) => { + cy.wrap($password.text()).as('password'); + }); + cy.get('button[type=button]').should('exist').should('be.visible').click(); + }); + cy.get('.ant-popconfirm').within(($popup) => { + cy.get('.ant-popover-buttons > button:nth-child(1)').should('exist').should('be.visible'); + cy.get('.ant-popover-buttons > .ant-btn-primary').should('exist').should('be.visible').click(); + }); + //verify employee is created + cy.get('#employeeTable > div').last().then(($row) => { + expect($row.text()).contains(EMPLOYEE_FIRST_NAME + ' ' + EMPLOYEE_LAST_NAME); + expect($row.text()).contains(EMPLOYEE_PHONE); + expect($row.text()).contains(EMPLOYEE_EMAIL); + }); + logout(); + //login as a new employee + openHDLoginPage(); + cy.get('@password').then(password => { + loginToHD(EMPLOYEE_EMAIL, password); + }) + cy.url().should('include', HEALTH_DEPARTMENT_TRACKING_ROUTE); + }); + }); +}); diff --git a/e2e/specs/locations/checkinOptions/cameraScannerCheckin.spec.js b/e2e/specs/locations/checkinOptions/cameraScannerCheckin.spec.js index 43f9e7f..42ef849 100644 --- a/e2e/specs/locations/checkinOptions/cameraScannerCheckin.spec.js +++ b/e2e/specs/locations/checkinOptions/cameraScannerCheckin.spec.js @@ -6,16 +6,15 @@ import { deleteGroup, } from '../helpers/functions'; import { + DEVICE_TYPES, traceDataPayload, createGroupPayload, - DEVICE_TYPES, } from '../helpers/functions.helper'; import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries.js'; import { - verifyLocationOverview, verifyScannerCounter, + verifyLocationOverview, } from '../helpers/inputValidation.helper'; -import { E2E_DEFAULT_LOCATION_FORM } from '../helpers/locations'; const CHECKIN_GROUP_NAME = 'neXenio'; diff --git a/e2e/specs/locations/checkinOptions/hardwareScannerCheckin.spec.js b/e2e/specs/locations/checkinOptions/hardwareScannerCheckin.spec.js index c134de7..8af394b 100644 --- a/e2e/specs/locations/checkinOptions/hardwareScannerCheckin.spec.js +++ b/e2e/specs/locations/checkinOptions/hardwareScannerCheckin.spec.js @@ -15,7 +15,6 @@ import { createGroup, deleteGroup, } from '../helpers/functions'; -import { E2E_DEFAULT_LOCATION_FORM } from '../helpers/locations'; const CHECKIN_GROUP_NAME = 'neXenio'; diff --git a/e2e/specs/locations/downloadPDF/downloadPDF.spec.js b/e2e/specs/locations/downloadPDF/downloadPDF.spec.js index cf5eee0..2017f99 100644 --- a/e2e/specs/locations/downloadPDF/downloadPDF.spec.js +++ b/e2e/specs/locations/downloadPDF/downloadPDF.spec.js @@ -32,9 +32,11 @@ describe('Download QR Codes PDF', () => { cy.getByCy('yes').click(); cy.getByCy('download').click(); cy.get('.ant-message-notice').should('exist'); - cy.get('.ant-message-notice').should('not.exist'); - cy.readFile('./downloads/luca_QRCode_Test Restaurant_General.pdf'); - cy.exec('rm ./downloads/luca_QRCode_Test\\ Restaurant_General.pdf'); + cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist'); + cy.readFile( + './downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf' + ); + cy.task('deleteFileIfExists', './downloads/luca_QRCode_Test Restaurant_Test Restaurant.pdf'); }); }); @@ -55,18 +57,16 @@ describe('Download QR Codes PDF', () => { cy.getByCy('done').click(); cy.getByCy('yes').click(); cy.getByCy('qrCodeDownload').click(); - cy.get('.ant-message-notice').should('not.exist'); + cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist'); cy.readFile( - './downloads/luca_QRCode_Nexenio_1 e2e_NEW_RESTAURANT_LOCATION.pdf' - ); - cy.exec( - 'rm ./downloads/luca_QRCode_Nexenio_1\\ e2e_NEW_RESTAURANT_LOCATION.pdf' + './downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf' ); + cy.task('deleteFileIfExists', './downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf'); }); }); describe('On Location page', () => { - it('downloads the Tables PDF', () => { + it('downloads the Tables PDF', { retries: 2 }, () => { cy.getByCy('location-NEW_RESTAURANT_LOCATION').click(); cy.getByCy('locationCard-tableSubdivision').click(); cy.getByCy('activateTableSubdivision').click(); @@ -74,11 +74,9 @@ describe('Download QR Codes PDF', () => { cy.getByCy('qrCodeDownload').click(); cy.get('.ant-message-notice', { timeout: 40000 }).should('not.exist'); cy.readFile( - './downloads/luca_QRCodes_Nexenio_1 e2e_NEW_RESTAURANT_LOCATION_Tables.pdf' - ); - cy.exec( - 'rm ./downloads/luca_QRCodes_Nexenio_1\\ e2e_NEW_RESTAURANT_LOCATION_Tables.pdf' + './downloads/luca_QRCodes_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION_Tables.pdf' ); + cy.task('deleteFileIfExists', './downloads/luca_QRCodes_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION_Tables.pdf'); }); it('downloads the Location PDF if tables are configured', () => { @@ -94,12 +92,10 @@ describe('Download QR Codes PDF', () => { cy.getByCy('qrCodeDownload').click(); cy.get('.ant-message-notice', { timeout: 20000 }).should('not.exist'); cy.readFile( - './downloads/luca_QRCode_Nexenio_1 e2e_NEW_RESTAURANT_LOCATION.pdf', + './downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf', { timeout: 20000 } ); - cy.exec( - 'rm ./downloads/luca_QRCode_Nexenio_1\\ e2e_NEW_RESTAURANT_LOCATION.pdf' - ); + cy.task('deleteFileIfExists', './downloads/luca_QRCode_Nexenio_1 e2e_Nexenio_1 e2e - NEW_RESTAURANT_LOCATION.pdf'); }); }); }); diff --git a/e2e/specs/locations/helpers/functions.js b/e2e/specs/locations/helpers/functions.js index c6ff6a4..dad684c 100644 --- a/e2e/specs/locations/helpers/functions.js +++ b/e2e/specs/locations/helpers/functions.js @@ -162,3 +162,9 @@ export const downloadLocationPrivateKeyFile = () => { cy.getByCy('checkPrivateKeyIsDownloaded').click(); cy.getByCy('finish').should('exist').click(); }; + +export const checkoutGuests = () => { + cy.getByCy('checkoutGuest').click(); + cy.get('.ant-popover-buttons .ant-btn-primary').click(); + cy.get('.successCheckout').should('exist'); +}; diff --git a/e2e/specs/locations/helpers/inputValidation.helper.js b/e2e/specs/locations/helpers/inputValidation.helper.js index 0eb7530..ebcaed0 100644 --- a/e2e/specs/locations/helpers/inputValidation.helper.js +++ b/e2e/specs/locations/helpers/inputValidation.helper.js @@ -3,6 +3,8 @@ import { E2E_SECOND_LOCATION_NAME, } from '../helpers/locations'; +const TIME_REGEXP = /^\d{2}:\d{2}$/; + export const checkRadiusInput = () => { // Invalid radius input (empty, under 50 or over 5000): cy.get('#radius').clear(); @@ -57,3 +59,26 @@ export const verifyModalWindowIsClosed = () => { expect($root.find('.ant-modal-content').length).to.equal(0); }); }; + +export const verifyGuestsCount = (count) => { + cy.getByCy('guestCount').next().click(); + cy.getByCy('guestCount').should('contain', count); +}; + +export const verifyCheckinGuestTime = (date) => { + cy.get('.ant-modal-content').should('be.visible').within(() => { + cy.getByCy('checkinDate').should('have.text', date); + cy.getByCy('checkinTime').contains(TIME_REGEXP); + cy.getByCy('checkoutDate').should('have.text', '-'); + cy.getByCy('checkoutTime').contains('-'); + }); +}; + +export const verifyCheckoutGuestTime = (date) => { + cy.get('.ant-modal-content').should('be.visible').within(() => { + cy.getByCy('checkinDate').should('have.text', date); + cy.getByCy('checkinTime').contains(TIME_REGEXP); + cy.getByCy('checkoutDate').should('have.text', date); + cy.getByCy('checkoutTime').contains(TIME_REGEXP); + }); +}; diff --git a/e2e/specs/locations/locationOverview/locationOverview.spec.js b/e2e/specs/locations/locationOverview/locationOverview.spec.js index 35e89b3..5df3546 100644 --- a/e2e/specs/locations/locationOverview/locationOverview.spec.js +++ b/e2e/specs/locations/locationOverview/locationOverview.spec.js @@ -1,55 +1,65 @@ -import { checkin, login } from '../helpers/functions'; -import { traceDataPayload, DEVICE_TYPES } from '../helpers/functions.helper'; +import moment from 'moment'; + import { DELETE_E2E_TRACE_QUERY } from '../helpers/dbQueries'; -import { E2E_DEFAULT_LOCATION_FORM } from '../helpers/locations'; +import { checkin, login, checkoutGuests } from '../helpers/functions'; +import { traceDataPayload, DEVICE_TYPES } from '../helpers/functions.helper'; +import { + verifyCheckinGuestTime, + verifyCheckoutGuestTime, + verifyGuestsCount, +} from '../helpers/inputValidation.helper'; -const checkTrackingTime = x => { - const filtered = x.split(' - ').filter(el => el !== ''); - return filtered.length; -}; +const CURRENT_DATE = moment().format('DD.MM.YYYY'); describe('Locations / Location overview', () => { - before(() => cy.executeQuery(DELETE_E2E_TRACE_QUERY)); - beforeEach(() => login()); + beforeEach(() => { + cy.executeQuery(DELETE_E2E_TRACE_QUERY); + login(); + }); describe('when view new location', () => { it('no checked-in guests is displayed by default', () => { - cy.getByCy('guestCount').should('contain', 0); - cy.getByCy('showGuestList').click({ force: true }); + verifyGuestsCount(0); + cy.getByCy('showGuestList').click(); cy.getByCy('totalCheckinCount').should('contain', 0); }); }); - describe('when check-in/check-out location', () => { - it('guest count and the tracking time is changed', () => { + describe('when check-in/check-out location with contact form', () => { + it('guest count and the tracking time are changed', () => { // Check in a guest checkin({ ...traceDataPayload, deviceType: DEVICE_TYPES.mobile }); // Expect the guest count to be 1 - cy.getByCy('guestCount').next().should('be.visible').click(); - cy.getByCy('guestCount').should('contain', 1); + verifyGuestsCount(1); // Expect the total checkin count to be 1 within the guest list modal - cy.getByCy('showGuestList').click({ force: true }); - cy.getByCy('totalCheckinCount').contains(1); - cy.getByCy('trackingTime').then(el => { - expect(checkTrackingTime(el.text())).to.equal(1); - }); + cy.getByCy('showGuestList').click(); + cy.getByCy('totalCheckinCount').should('exist').and('contain', 1); + verifyCheckinGuestTime(CURRENT_DATE); cy.get('.ant-modal-close-x').click(); - // Check out the guest - cy.getByCy('checkoutGuest').click(); - cy.get('.ant-popover-buttons .ant-btn-primary').click(); - cy.get('.successCheckout').should('exist'); + checkoutGuests(); // Expect the total checkin count to be 1 in the guest list modal - cy.getByCy('showGuestList').click({ force: true }); + cy.getByCy('showGuestList').click(); + verifyCheckoutGuestTime(CURRENT_DATE); + }); + }); + + describe('when check-in/check-out location with camera scanner', () => { + it('guest count and the tracking time are changed', () => { + // Check in a guest + checkin({ ...traceDataPayload, deviceType: DEVICE_TYPES.mobile }); + // Expect the guest count to be 1 + verifyGuestsCount(1); + // Expect the total checkin count to be 1 within the guest list modal + cy.getByCy('showGuestList').click(); cy.getByCy('totalCheckinCount').should('exist').and('contain', 1); - // Expect the both checkin and checkout time exist in the guest list modal - cy.getByCy('showGuestList').click({ force: true }); - cy.getByCy('trackingTime').then(el => { - setTimeout( - () => expect(checkTrackingTime(el.text())).to.equal(2), - 3000 - ); - }); + verifyCheckinGuestTime(CURRENT_DATE); + cy.get('.ant-modal-close-x').click(); + // Check out the guest + checkoutGuests(); + // Expect the total checkin count to be 1 in the guest list modal + cy.getByCy('showGuestList').click(); + verifyCheckoutGuestTime(CURRENT_DATE); }); }); }); diff --git a/e2e/specs/webapp/checkin/checkinByLinkFlow.spec.js b/e2e/specs/webapp/checkin/checkinByLinkFlow.spec.js index 3f40d98..8ff8c1d 100644 --- a/e2e/specs/webapp/checkin/checkinByLinkFlow.spec.js +++ b/e2e/specs/webapp/checkin/checkinByLinkFlow.spec.js @@ -1,14 +1,17 @@ -import {createGroupPayload} from "../../locations/helpers/functions.helper"; -import {basicLocationLogin, createGroup, deleteGroup, logout} from "../../locations/helpers/functions"; - -import {clearDatabase} from "../helpers/database"; -import {registerDevice} from "../helpers/functions"; -import {clean} from "../../workflow/helpers/functions"; +import { createGroupPayload } from '../../locations/helpers/functions.helper'; import { - addHealthDepartmentPrivateKeyFile, -} from "../../health-department/helper/ui/login.helper"; -import {loginHealthDepartment} from "../../health-department/helper/api/auth.helper"; -import {WEBAPP_ROUTE} from "../helpers/routes"; + basicLocationLogin, + createGroup, + deleteGroup, + logout, +} from '../../locations/helpers/functions'; + +import { clearDatabase } from '../helpers/database'; +import { registerDevice } from '../helpers/functions'; +import { clean } from '../../workflow/helpers/functions'; +import { addHealthDepartmentPrivateKeyFile } from '../../health-department/helper/ui/login.helper'; +import { loginHealthDepartment } from '../../health-department/helper/api/auth.helper'; +import { WEBAPP_ROUTE, LOCATIONS_ROUTE } from '../helpers/routes'; describe('WebApp / CheckIn', () => { before(() => { @@ -21,40 +24,41 @@ describe('WebApp / CheckIn', () => { cy.wait(1000); registerDevice(); }); - after(() => { - basicLocationLogin(); - cy.get('@groupId').then(groupId => { - deleteGroup(groupId); - }); - logout(); - cy.wrap(clearDatabase()); + after(() => { + basicLocationLogin(); + cy.get('@groupId').then(groupId => { + deleteGroup(groupId); }); - describe('when an user checkin by link', () => { - it('open the checkout screen', () => { - cy.get('@scannerId').then(scannerId => { - cy.visit(`${WEBAPP_ROUTE}/${scannerId}`); - }); - cy.url().should('contain', '/checkout') - cy.getByCy('locationName').should('contain', createGroupPayload.name); + logout(); + cy.wrap(clearDatabase()); + }); + describe('when an user checks in by link', () => { + it('opens the checkout screen', () => { + cy.visit(LOCATIONS_ROUTE); + cy.get('@scannerId').then(scannerId => { + cy.visit(`${WEBAPP_ROUTE}/${scannerId}`); + }); + cy.url().should('contain', '/checkout'); + cy.getByCy('locationName').should('contain', createGroupPayload.name); - // Simulate clock - cy.clock(Date.now(), ['Date', 'setInterval', 'clearInterval']) + // Simulate clock + cy.clock(Date.now(), ['Date', 'setInterval', 'clearInterval']); - // Simulate hook ticks - for (let i = 0; i < 10; i++) { - cy.tick(1000); - } - // 2 Minutes - cy.tick(120000); + // Simulate hook ticks + for (let i = 0; i < 10; i++) { + cy.tick(1000); + } + // 2 Minutes + cy.tick(120000); - // Simulate hook ticks - for (let i = 0; i < 10; i++) { - cy.tick(1000); - } - cy.getByCy('clockMinutes').should('contain', '02'); - cy.getByCy('checkout').click().tick(1000); - cy.url().should('not.contain', '/checkout') - cy.clock().then((clock) => clock.restore()); - }); + // Simulate hook ticks + for (let i = 0; i < 10; i++) { + cy.tick(1000); + } + cy.getByCy('clockMinutes').should('contain', '02'); + cy.getByCy('checkout').click().tick(1000); + cy.url().should('not.contain', '/checkout'); + cy.clock().then(clock => clock.restore()); }); + }); }); diff --git a/e2e/specs/webapp/helpers/routes.js b/e2e/specs/webapp/helpers/routes.js index be5adff..99b87a8 100644 --- a/e2e/specs/webapp/helpers/routes.js +++ b/e2e/specs/webapp/helpers/routes.js @@ -1 +1,2 @@ export const WEBAPP_ROUTE = '/webapp'; +export const LOCATIONS_ROUTE = '/'; diff --git a/e2e/specs/workflow/senarios/contactForm.spec.js b/e2e/specs/workflow/senarios/contactForm.spec.js index efd9ae0..be14c0a 100644 --- a/e2e/specs/workflow/senarios/contactForm.spec.js +++ b/e2e/specs/workflow/senarios/contactForm.spec.js @@ -4,10 +4,7 @@ import { fillForm } from '../../contact-form/helpers/functions'; import { loginHealthDepartment } from '../../health-department/helper/api/auth.helper'; -import { - downloadHealthDepartmentPrivateKey, - addHealthDepartmentPrivateKeyFile, -} from '../../health-department/helper/ui/login.helper'; +import { addHealthDepartmentPrivateKeyFile } from '../../health-department/helper/ui/login.helper'; import { APP_ROUTE } from '../../locations/helpers/routes'; import { createGroupPayload } from '../../locations/helpers/functions.helper'; @@ -20,7 +17,15 @@ import { } from '../../locations/helpers/functions'; import { clean } from '../helpers/functions'; -import { E2E_COMPLETE_EMAIL, E2E_COMPLETE_PASSWORD, WORKFLOW_LOCATION_PRIVATE_KEY_PATH } from '../helpers/users'; +import { + E2E_COMPLETE_EMAIL, + E2E_COMPLETE_PASSWORD, + WORKFLOW_LOCATION_PRIVATE_KEY_PATH, +} from '../helpers/users'; +import { + setDatePickerStartDate, + setDatePickerEndDate, +} from '../../health-department/helper/ui/tracking.helper'; const FORM_WORKFLOW_TESTING_GROUP_NAME = 'Form Workflow'; @@ -56,14 +61,10 @@ context('Workflow', () => { cy.get('.ant-modal-close-x').click(); // Checkin with contact form cy.log('Checkin with in testing Location with Contact Form'); - cy.window().then(win => { - cy.stub(win, 'open', link => { - win.location.href = link; - }); - }); + cy.stubNewWindow(); cy.getByCy('contactForm').click(); const users = []; - for (let index = 0; index < 5; index += 1) { + for (let index = 0; index < 2; index += 1) { users.push(fillForm()); } logout(); @@ -77,27 +78,8 @@ context('Workflow', () => { cy.getByCy('groupNameInput').type(FORM_WORKFLOW_TESTING_GROUP_NAME); cy.getByCy('startGroupSearch').click(); cy.getByCy(`group_${FORM_WORKFLOW_TESTING_GROUP_NAME}`).click(); - cy.get('input[id=startDate]').should('exist'); - cy.get('input[id=startDate]').click(); - cy.get('input[id=startDate]').type(`${yesterdayDate}{enter}`); - cy.get('input[id=startTime]').should('exist'); - cy.get('input[id=startTime]').click(); - cy.get('.ant-picker-time-panel').should('exist'); - cy.get('.ant-picker-time-panel').within(() => { - cy.get('.ant-picker-time-panel-cell').eq(0).click().type('{enter}'); - }); - - cy.get('input[id=endDate]').should('exist'); - cy.get('input[id=endDate]').click(); - cy.get('input[id=endDate]').type(`${tomorrowDate}{enter}`); - cy.get('input[id=endTime]').should('exist'); - cy.get('input[id=endTime]').click(); - cy.get('.ant-picker-time-panel').should('exist'); - cy.get('.ant-picker-time-panel') - .eq(1) - .within(() => { - cy.get('.ant-picker-time-panel-cell').eq(0).click().type('{enter}'); - }); + setDatePickerStartDate(yesterdayDate); + setDatePickerEndDate(tomorrowDate); cy.getByCy('requestGroupData').click(); cy.getByCy('processEntry').should('exist'); @@ -120,11 +102,7 @@ context('Workflow', () => { basicLocationLogin(E2E_COMPLETE_EMAIL, E2E_COMPLETE_PASSWORD); cy.visit(APP_ROUTE); cy.getByCy('dataRequests').click(); - cy.window().then(win => { - cy.stub(win, 'open', link => { - win.location.href = link; - }); - }); + cy.stubNewWindow(); cy.getByCy('completeDataTransfer').first().click(); uploadLocationPrivateKeyFile(WORKFLOW_LOCATION_PRIVATE_KEY_PATH); cy.getByCy('next').click(); diff --git a/package.json b/package.json index ea8bf4d..4d9da2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/web", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/backend/package.json b/services/backend/package.json index 044afc1..bfed271 100644 --- a/services/backend/package.json +++ b/services/backend/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/backend", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", @@ -63,6 +63,7 @@ "prom-client": "13.1.0", "rate-limit-redis": "2.1.0", "redis": "3.1.2", + "response-time": "2.3.2", "sequelize": "6.5.1", "sequelize-cli": "6.2.0", "swagger-jsdoc": "6.1.0", diff --git a/services/backend/src/app.js b/services/backend/src/app.js index 3089224..eba8b4f 100644 --- a/services/backend/src/app.js +++ b/services/backend/src/app.js @@ -17,6 +17,7 @@ const passportSession = require('./passport/session'); const bearerBadgeGeneratorStrategy = require('./passport/bearerBadgeGenerator'); const localOperatorStrategy = require('./passport/localOperator'); const localHealthDepartmentEmployeeStrategy = require('./passport/localHealthDepartmentEmployee'); +const requestMetricsMiddleware = require('./middlewares/requestMetrics'); // Routes const versionRouter = require('./routes/version'); @@ -75,6 +76,7 @@ const configureApp = () => { if (!config.get('debug')) { app.use(helmet.hsts()); } + app.use(requestMetricsMiddleware); app.use(cookieParser()); app.use( session({ diff --git a/services/backend/src/database/migrations/2021.07.20-1310-addIndexOnDeviceTypeToUsersTable.js b/services/backend/src/database/migrations/2021.07.20-1310-addIndexOnDeviceTypeToUsersTable.js new file mode 100644 index 0000000..891a830 --- /dev/null +++ b/services/backend/src/database/migrations/2021.07.20-1310-addIndexOnDeviceTypeToUsersTable.js @@ -0,0 +1,13 @@ +module.exports = { + up: async queryInterface => { + await queryInterface.addIndex('Users', { + name: 'users_device_type_index', + fields: ['deviceType'], + concurrently: true, + }); + }, + + down: async queryInterface => { + await queryInterface.removeIndex('Users', 'users_device_type_index'); + }, +}; diff --git a/services/backend/src/database/models/index.js b/services/backend/src/database/models/index.js index 486b45a..b4b26a9 100644 --- a/services/backend/src/database/models/index.js +++ b/services/backend/src/database/models/index.js @@ -6,6 +6,7 @@ const Sequelize = require('sequelize'); const environment = config.util.getEnv('NODE_ENV'); const logger = require('../../utils/logger'); +const { client } = require('../../utils/metrics'); const databaseConfig = require('../config.js')[environment]; const basename = path.basename(__filename); @@ -29,6 +30,28 @@ const database = new Sequelize({ }, }); +const counter = new client.Counter({ + name: 'sequelize_table_creation_count', + help: 'Total database rows created since process start.', + labelNames: ['tableName'], +}); + +database.addHook('beforeCreate', instance => { + counter.inc({ + tableName: instance.constructor.name, + }); +}); + +database.addHook('beforeBulkCreate', instances => { + if (instances.length <= 0) return; + counter.inc( + { + tableName: instances[0].constructor.name, + }, + instances.length + ); +}); + /* eslint-disable-next-line security/detect-non-literal-fs-filename */ fs.readdirSync(__dirname) .filter( diff --git a/services/backend/src/middlewares/requestMetrics.js b/services/backend/src/middlewares/requestMetrics.js new file mode 100644 index 0000000..7f12f9d --- /dev/null +++ b/services/backend/src/middlewares/requestMetrics.js @@ -0,0 +1,51 @@ +const responseTime = require('response-time'); + +const { client } = require('../utils/metrics'); + +const labelNames = ['method', 'statusCode', 'path']; + +const histogram = new client.Histogram({ + name: 'http_request_duration_seconds', + help: 'A histogram of the HTTP request durations in seconds.', + buckets: [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10], + labelNames, +}); + +const summary = new client.Summary({ + name: 'summary_http_request_duration_seconds', + help: 'A summary of the HTTP request durations in seconds.', + percentiles: [0.5, 0.75, 0.95, 0.98, 0.99, 0.999], + labelNames, +}); + +const counter = new client.Counter({ + name: 'http_requests_total', + help: 'The total number of handled HTTP requests.', + labelNames, +}); + +module.exports = responseTime((request, response, time) => { + const { method, route, baseUrl } = request; + const { statusCode } = response; + + if (!route) return; + const path = `${baseUrl}${route.path}`.toLowerCase(); + + histogram.observe( + { + method, + path, + statusCode, + }, + time / 1000 + ); + summary.observe( + { + method, + path, + statusCode, + }, + time / 1000 + ); + counter.inc({ method, path, statusCode }); +}); diff --git a/services/backend/src/routes/v3/locationTransfers.js b/services/backend/src/routes/v3/locationTransfers.js index 71123aa..51ea5ba 100644 --- a/services/backend/src/routes/v3/locationTransfers.js +++ b/services/backend/src/routes/v3/locationTransfers.js @@ -368,6 +368,7 @@ router.get( uuid: department.uuid, name: department.name, publicHDEKP: department.publicHDEKP, + signedPublicHDEKP: department.signedPublicHDEKP, }, location: { groupId: location.LocationGroup.uuid, diff --git a/services/backend/src/routes/v3/tests.openapi.yaml b/services/backend/src/routes/v3/tests.openapi.yaml index 9fe8da1..985cd6f 100644 --- a/services/backend/src/routes/v3/tests.openapi.yaml +++ b/services/backend/src/routes/v3/tests.openapi.yaml @@ -25,6 +25,7 @@ paths: - tag delete: summary: Delete a redeemed test + tags: [Tests] requestBody: content: application/json: diff --git a/services/backend/src/routes/v3/tracingProcesses.openapi.yaml b/services/backend/src/routes/v3/tracingProcesses.openapi.yaml index f7724da..91e49da 100644 --- a/services/backend/src/routes/v3/tracingProcesses.openapi.yaml +++ b/services/backend/src/routes/v3/tracingProcesses.openapi.yaml @@ -137,6 +137,8 @@ paths: description: Update tracing process information. delete: summary: Delete Tracing Process + tags: + - TracingProcesses responses: 204: description: Success diff --git a/services/backend/src/utils/validation.test.js b/services/backend/src/utils/validation.test.js index 0d348dc..062c717 100644 --- a/services/backend/src/utils/validation.test.js +++ b/services/backend/src/utils/validation.test.js @@ -18,7 +18,12 @@ const PHONE_NUMBER_TEST_CASES_INVALID = [ 'notanumber', ]; -const SAFE_STRING_TEST_CASES_VALID = ['015100123123', 'Standort', 'a@bc.de']; +const SAFE_STRING_TEST_CASES_VALID = [ + '015100123123', + 'Standort', + 'a@bc.de', + 'CMD()', +]; const SAFE_STRING_TEST_CASES_INVALID = [ "Carl's Bar", '100$ Bill', diff --git a/services/backend/src/utils/validation.ts b/services/backend/src/utils/validation.ts index 538f414..26e0557 100644 --- a/services/backend/src/utils/validation.ts +++ b/services/backend/src/utils/validation.ts @@ -11,7 +11,7 @@ import { DEVICE_TYPE_FORM, } from 'constants/deviceTypes'; -const SAFE_CHARACTERS_REGEX = /^[\w !&+./:@`|£À-ÿÄăąćĉċÄđēėęěÄğģĥħĩīįİıĵķĸĺļłńņÅőœŗřśÅşšţŦũūÅůűųŵŷźżžơưếệ–-]*$/i; +const SAFE_CHARACTERS_REGEX = /^[\w !&()+,./:@`|£À-ÿÄăąćĉċÄđēėęěÄğģĥħĩīįİıĵķĸĺļłńņÅőœŗřśÅşšţŦũūÅůűųŵŷźżžơưếệ–-]*$/i; const NO_HTTP_REGEX = /^((?!http).)*$/i; const PASSWORD_REQUIREMENTS = { diff --git a/services/backend/yarn.lock b/services/backend/yarn.lock index 47ed2aa..a8b8bee 100644 --- a/services/backend/yarn.lock +++ b/services/backend/yarn.lock @@ -54,19 +54,19 @@ integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== "@babel/core@^7.12.16", "@babel/core@^7.7.5": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" - integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" + integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== dependencies: "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.14.5" + "@babel/generator" "^7.14.8" "@babel/helper-compilation-targets" "^7.14.5" - "@babel/helper-module-transforms" "^7.14.5" - "@babel/helpers" "^7.14.6" - "@babel/parser" "^7.14.6" + "@babel/helper-module-transforms" "^7.14.8" + "@babel/helpers" "^7.14.8" + "@babel/parser" "^7.14.8" "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -83,12 +83,12 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.5.tgz#848d7b9f031caca9d0cd0af01b063f226f52d785" - integrity sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA== +"@babel/generator@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.8.tgz#bf86fd6af96cf3b74395a8ca409515f89423e070" + integrity sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg== dependencies: - "@babel/types" "^7.14.5" + "@babel/types" "^7.14.8" jsesc "^2.5.1" source-map "^0.5.0" @@ -139,19 +139,19 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-module-transforms@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz#7de42f10d789b423eb902ebd24031ca77cb1e10e" - integrity sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA== +"@babel/helper-module-transforms@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz#d4279f7e3fd5f4d5d342d833af36d4dd87d7dc49" + integrity sha512-RyE+NFOjXn5A9YU1dkpeBaduagTlZ0+fccnIcAGbv1KGUlReBj7utF7oEth8IdIBQPcux0DDgW5MFBH2xu9KcA== dependencies: "@babel/helper-module-imports" "^7.14.5" "@babel/helper-replace-supers" "^7.14.5" - "@babel/helper-simple-access" "^7.14.5" + "@babel/helper-simple-access" "^7.14.8" "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.8" "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" "@babel/helper-optimise-call-expression@^7.14.5": version "7.14.5" @@ -170,12 +170,12 @@ "@babel/traverse" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helper-simple-access@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz#66ea85cf53ba0b4e588ba77fc813f53abcaa41c4" - integrity sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw== +"@babel/helper-simple-access@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" + integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== dependencies: - "@babel/types" "^7.14.5" + "@babel/types" "^7.14.8" "@babel/helper-split-export-declaration@^7.14.5": version "7.14.5" @@ -184,24 +184,24 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-validator-identifier@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8" - integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg== +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" + integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== -"@babel/helpers@^7.14.6": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635" - integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA== +"@babel/helpers@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.8.tgz#839f88f463025886cff7f85a35297007e2da1b77" + integrity sha512-ZRDmI56pnV+p1dH6d+UN6GINGz7Krps3+270qqI9UJ4wxYThfAIcI5i7j5vXC4FJ3Wap+S9qcebxeYiqn87DZw== dependencies: "@babel/template" "^7.14.5" - "@babel/traverse" "^7.14.5" - "@babel/types" "^7.14.5" + "@babel/traverse" "^7.14.8" + "@babel/types" "^7.14.8" "@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": version "7.14.5" @@ -212,10 +212,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.14.7": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595" - integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA== +"@babel/parser@^7.14.5", "@babel/parser@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" + integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA== "@babel/template@^7.14.5": version "7.14.5" @@ -226,27 +226,27 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/traverse@^7.14.5": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.7.tgz#64007c9774cfdc3abd23b0780bc18a3ce3631753" - integrity sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ== +"@babel/traverse@^7.14.5", "@babel/traverse@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.8.tgz#c0253f02677c5de1a8ff9df6b0aacbec7da1a8ce" + integrity sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg== dependencies: "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.14.5" + "@babel/generator" "^7.14.8" "@babel/helper-function-name" "^7.14.5" "@babel/helper-hoist-variables" "^7.14.5" "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/parser" "^7.14.7" - "@babel/types" "^7.14.5" + "@babel/parser" "^7.14.8" + "@babel/types" "^7.14.8" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.5.tgz#3bb997ba829a2104cedb20689c4a5b8121d383ff" - integrity sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg== +"@babel/types@^7.14.5", "@babel/types@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.8.tgz#38109de8fcadc06415fbd9b74df0065d4d41c728" + integrity sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q== dependencies: - "@babel/helper-validator-identifier" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.8" to-fast-properties "^2.0.0" "@dabh/diagnostics@^2.0.2": @@ -259,9 +259,9 @@ kuler "^2.0.0" "@eslint/eslintrc@^0.4.0": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179" - integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg== + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -331,9 +331,9 @@ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#94c23db18ee4653e129abd26fb06f870ac9e1ee2" - integrity sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA== + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" @@ -382,9 +382,9 @@ integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== "@tsconfig/node16@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1" - integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA== + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== "@types/body-parser@*": version "1.19.1" @@ -395,9 +395,9 @@ "@types/node" "*" "@types/chai@4": - version "4.2.20" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.20.tgz#bb02e7d02b6c452f6bbb8fe575b56e180644de67" - integrity sha512-E121rHk/4BlcEwANZOwcHl8L/Sl0zyIFXJoyggXkl7FCT/4MTf5u25f+qiphe0V5ELaFIkCptgvbf4whCJUVMA== + version "4.2.21" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.21.tgz#9f35a5643129df132cf3b5c1ec64046ea1af0650" + integrity sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg== "@types/config@0.0.39": version "0.0.39" @@ -497,9 +497,9 @@ integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== "@types/node@*": - version "16.0.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.1.tgz#70cedfda26af7a2ca073fdcc9beb2fff4aa693f8" - integrity sha512-hBOx4SUlEPKwRi6PrXuTGw1z6lz0fjsibcWCM378YxsSu/6+C30L6CR49zIBKHiwNWCYIcOLjg4OHKZaFeLAug== + version "16.4.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.5.tgz#eac95d4e52775190c405f0b9061ddcfb0304f7fc" + integrity sha512-+0GPv/hIFNoy8r5MFf7vRpBjnqNYNrlHdetoy23E7TYc7JB2ctwyi3GMKpviozaHQ/Sy2kBNUTvG9ywN66zV1g== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -652,9 +652,9 @@ accepts@~1.3.7: negotiator "0.6.2" acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^7.4.0: version "7.4.1" @@ -687,9 +687,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.6.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.1.tgz#ae65764bf1edde8cd861281cda5057852364a295" - integrity sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ== + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -1063,9 +1063,9 @@ camelcase@^6.0.0: integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-lite@^1.0.30001219: - version "1.0.30001243" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz#d9250155c91e872186671c523f3ae50cfc94a3aa" - integrity sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA== + version "1.0.30001248" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001248.tgz#26ab45e340f155ea5da2920dadb76a533cb8ebce" + integrity sha512-NwlQbJkxUFJ8nMErnGtT0QTM2TJ33xgz4KXJSMIrjXIbDVdaYueGyjOrLKRtJC+rTiWfi6j5cnZN1NBiSBJGNw== caseless@~0.12.0: version "0.12.0" @@ -1257,9 +1257,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.5" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.5.tgz#65474a8f0e7439625f3d27a6a19d89fc45223014" - integrity sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -1566,7 +1566,7 @@ denque@^1.5.0: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== -depd@~1.1.2: +depd@~1.1.0, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -1671,9 +1671,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.723: - version "1.3.770" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.770.tgz#a9e705a73315f4900880622b3ab76cf1d7221b77" - integrity sha512-Kyh8DGK1KfEZuYKIHvuOmrKotsKZQ+qBkDIWHciE3QoFkxXB1KzPP+tfLilSHAfxTON0yYMnFCWkQtUOR7g6KQ== + version "1.3.789" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.789.tgz#c3ea060ba1e36e41c87943a47ed2daadc545be2b" + integrity sha512-lK4xn6C6ZF1kgLaC/EhOtC1MSKENExj3rMwGVnBTfHW81Z/Hb1Rge5YaWawN/YOXy3xCaESuE4KWSD50kOZ9rQ== elliptic@6.5.4: version "6.5.4" @@ -2223,7 +2223,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.0.3: +fast-glob@^3.0.3, fast-glob@^3.1.1: version "3.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== @@ -2234,17 +2234,6 @@ fast-glob@^3.0.3: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.1.1: - version "3.2.6" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.6.tgz#434dd9529845176ea049acc9343e8282765c6e1a" - integrity sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2256,9 +2245,9 @@ fast-levenshtein@^2.0.6: integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fast-safe-stringify@^2.0.4: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" - integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + version "2.0.8" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" + integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== fast-sha256@1.3.0: version "1.3.0" @@ -2357,9 +2346,9 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.0.tgz#da07fb8808050aba6fdeac2294542e5043583f05" - integrity sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A== + version "3.2.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.1.tgz#bbef080d95fca6709362c73044a1634f7c6e7d05" + integrity sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg== fn.name@1.x.x: version "1.1.0" @@ -2897,9 +2886,9 @@ is-callable@^1.1.4, is-callable@^1.2.3: integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-core-module@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + version "2.5.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" + integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== dependencies: has "^1.0.3" @@ -2988,9 +2977,9 @@ is-set@^2.0.1, is-set@^2.0.2: integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-string@^1.0.5, is-string@^1.0.6: version "1.0.6" @@ -3575,17 +3564,17 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" -mime-db@1.48.0: - version "1.48.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== +mime-db@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.31" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" - integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== dependencies: - mime-db "1.48.0" + mime-db "1.49.0" mime@1.6.0, mime@^1.4.1: version "1.6.0" @@ -3932,9 +3921,9 @@ object-assign@^4, object-assign@^4.1.0: integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= object-inspect@^1.10.3, object-inspect@^1.9.0: - version "1.10.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" - integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-is@^1.1.4: version "1.1.5" @@ -3984,7 +3973,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: +on-headers@~1.0.1, on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== @@ -4245,9 +4234,9 @@ pg-int8@1.0.1: integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== pg-pool@^3.2.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.3.0.tgz#12d5c7f65ea18a6e99ca9811bd18129071e562fc" - integrity sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg== + version "3.4.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" + integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== pg-protocol@^1.4.0: version "1.5.0" @@ -4680,6 +4669,14 @@ resolve@^1.0.0, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.17 is-core-module "^2.2.0" path-parse "^1.0.6" +response-time@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/response-time/-/response-time-2.3.2.tgz#ffa71bab952d62f7c1d49b7434355fbc68dffc5a" + integrity sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo= + dependencies: + depd "~1.1.0" + on-headers "~1.0.1" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -5244,9 +5241,9 @@ tar@^2.0.0: inherits "2" tar@^4: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + version "4.4.15" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.15.tgz#3caced4f39ebd46ddda4d6203d48493a919697f8" + integrity sha512-ItbufpujXkry7bHH9NpQyTXPbJ72iTlXgkBAYsAjDXk3Ds8t/3NfO5P4xZGy7u+sYuQUbimgzswX4uQIEeNVOA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" @@ -5584,7 +5581,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validator@13.6.0: +validator@13.6.0, validator@^13.6.0: version "13.6.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.6.0.tgz#1e71899c14cdc7b2068463cb24c1cc16f6ec7059" integrity sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg== @@ -5594,11 +5591,6 @@ validator@^10.11.0: resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== -validator@^12.0.0: - version "12.2.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-12.2.0.tgz#660d47e96267033fd070096c3b1a6f2db4380a0a" - integrity sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ== - vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -5908,13 +5900,13 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== z-schema@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-4.2.3.tgz#85f7eea7e6d4fe59a483462a98f511bd78fe9882" - integrity sha512-zkvK/9TC6p38IwcrbnT3ul9in1UX4cm1y/VZSs4GHKIiDCrlafc+YQBgQBUdDXLAoZHf2qvQ7gJJOo6yT1LH6A== + version "4.2.4" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-4.2.4.tgz#73102a49512179b12a8ec50b1daa676b984da6e4" + integrity sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w== dependencies: lodash.get "^4.4.2" lodash.isequal "^4.5.0" - validator "^12.0.0" + validator "^13.6.0" optionalDependencies: commander "^2.7.1" diff --git a/services/contact-form/.eslintrc.js b/services/contact-form/.eslintrc.js index 9799b69..a5bfd3b 100644 --- a/services/contact-form/.eslintrc.js +++ b/services/contact-form/.eslintrc.js @@ -54,6 +54,7 @@ module.exports = { 'import/prefer-default-export': 0, 'import/no-default-export': 2, 'import/no-namespace': 0, + 'unicorn/better-regex': 1, 'unicorn/filename-case': 0, 'unicorn/no-null': 0, 'unicorn/no-array-for-each': 0, diff --git a/services/contact-form/.iyarc b/services/contact-form/.iyarc index 1394716..f8ab921 100644 --- a/services/contact-form/.iyarc +++ b/services/contact-form/.iyarc @@ -17,3 +17,15 @@ # Regular Expression Denial of Service (ReDoS) # This is only used by react-scripts for development purposes. 1755 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32804 +# Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization +# This is only used by react-scripts for development purposes. +1770 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32803 +# Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning +# This is only used by react-scripts for development purposes. +1771 \ No newline at end of file diff --git a/services/contact-form/package.json b/services/contact-form/package.json index 60fe685..d4bf683 100644 --- a/services/contact-form/package.json +++ b/services/contact-form/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/contact-form", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/contact-form/src/components/InputForm/AddressInput/AddressInput.react.js b/services/contact-form/src/components/InputForm/AddressInput/AddressInput.react.js index b0b7000..9e8f177 100644 --- a/services/contact-form/src/components/InputForm/AddressInput/AddressInput.react.js +++ b/services/contact-form/src/components/InputForm/AddressInput/AddressInput.react.js @@ -2,7 +2,12 @@ import React from 'react'; import { Col, Input, Row, Grid } from 'antd'; -import { useAddressValidator } from 'components/hooks/useValidators'; +import { + useStreetValidator, + useHouseNoValidator, + useZipCodValidator, + useCityValidator, +} from 'components/hooks/useValidators'; import { FormItem } from '../FormItem'; @@ -11,12 +16,10 @@ const { useBreakpoint } = Grid; export const AddressInput = ({ formFieldNames }) => { const screens = useBreakpoint(); - const { - streetValidator, - houseNoValidator, - zipCodValidator, - cityValidator, - } = useAddressValidator(); + const streetValidator = useStreetValidator('street'); + const houseNoValidator = useHouseNoValidator(); + const zipCodValidator = useZipCodValidator(); + const cityValidator = useCityValidator('city'); return ( <> diff --git a/services/contact-form/src/components/InputForm/InputForm.react.js b/services/contact-form/src/components/InputForm/InputForm.react.js index 5c68426..1ce4e9a 100644 --- a/services/contact-form/src/components/InputForm/InputForm.react.js +++ b/services/contact-form/src/components/InputForm/InputForm.react.js @@ -11,7 +11,7 @@ import { PRIVACY_LINK } from 'constants/links'; // Hooks import { useRegister } from 'components/hooks/useRegister'; import { - useNameValidator, + usePersonNameValidator, usePhoneValidator, useEmailValidator, useTableValidator, @@ -44,10 +44,11 @@ export const InputForm = ({ const [isSubmitting, setIsSubmitting] = useState(false); const formReference = useRef(null); - const { firstNameValidator, lastNameValidator } = useNameValidator(); - const { phoneValidator } = usePhoneValidator(); - const { emailValidator } = useEmailValidator(); - const { tableValidator } = useTableValidator(); + const firstNameValidator = usePersonNameValidator('firstName'); + const lastNameValidator = usePersonNameValidator('lastName'); + const phoneValidator = usePhoneValidator(); + const emailValidator = useEmailValidator(); + const tableValidator = useTableValidator(); const { isLoading, error, data: dailyKey } = useQuery( 'dailyKey', diff --git a/services/contact-form/src/components/hooks/useValidators.js b/services/contact-form/src/components/hooks/useValidators.js index c6badb3..1a6ab91 100644 --- a/services/contact-form/src/components/hooks/useValidators.js +++ b/services/contact-form/src/components/hooks/useValidators.js @@ -1,11 +1,10 @@ import { useIntl } from 'react-intl'; +import { useMemo } from 'react'; import { MAX_NAME_LENGTH, MAX_CITY_LENGTH, - MAX_EMAIL_LENGTH, MAX_STREET_LENGTH, - MAX_POSTAL_CODE_LENGTH, MAX_HOUSE_NUMBER_LENGTH, } from 'constants/valueLength'; @@ -14,84 +13,76 @@ import { getZipCodeRule, getPhoneRule, getEmailRule, - getNamesRule, + getStringsRule, getMaxLengthRule, getTableNoRule, getHouseNoRule, } from 'utils/validatorRules'; -import { - invalidFirstName, - invalidLastName, - invalidStreet, - invalidZipCode, - invalidCity, - invalidPhone, - invalidEmail, - invalidHouseNo, - invalidTableNo, -} from 'constants/errorMessages'; +export const usePersonNameValidator = fieldName => { + const intl = useIntl(); + return useMemo( + () => [ + getRequiredRule(intl), + getStringsRule(intl, fieldName), + getMaxLengthRule(intl, MAX_NAME_LENGTH), + ], + [intl, fieldName] + ); +}; + +export const useStreetValidator = fieldName => { + const intl = useIntl(); + return useMemo( + () => [ + getRequiredRule(intl), + getStringsRule(intl, fieldName), + getMaxLengthRule(intl, MAX_STREET_LENGTH), + ], + [intl, fieldName] + ); +}; + +export const useHouseNoValidator = () => { + const intl = useIntl(); + return useMemo( + () => [ + getRequiredRule(intl), + getHouseNoRule(intl), + getMaxLengthRule(intl, MAX_HOUSE_NUMBER_LENGTH), + ], + [intl] + ); +}; -export const useNameValidator = () => { +export const useZipCodValidator = () => { const intl = useIntl(); - const firstNameValidator = [ - getRequiredRule(intl), - getNamesRule(intl, invalidFirstName), - getMaxLengthRule(intl, MAX_NAME_LENGTH), - ]; - const lastNameValidator = [ - getRequiredRule(intl), - getNamesRule(intl, invalidLastName), - getMaxLengthRule(intl, MAX_NAME_LENGTH), - ]; - return { firstNameValidator, lastNameValidator }; + return useMemo(() => [getRequiredRule(intl), getZipCodeRule(intl)], [intl]); }; -export const useAddressValidator = () => { +export const useCityValidator = fieldName => { const intl = useIntl(); - const streetValidator = [ - getRequiredRule(intl), - getNamesRule(intl, invalidStreet), - getMaxLengthRule(intl, MAX_STREET_LENGTH), - ]; - const houseNoValidator = [ - getRequiredRule(intl), - getHouseNoRule(intl, invalidHouseNo), - getMaxLengthRule(intl, MAX_HOUSE_NUMBER_LENGTH), - ]; - const zipCodValidator = [ - getRequiredRule(intl), - getZipCodeRule(intl, invalidZipCode), - getMaxLengthRule(intl, MAX_POSTAL_CODE_LENGTH), - ]; - const cityValidator = [ - getRequiredRule(intl), - getNamesRule(intl, invalidCity), - getMaxLengthRule(intl, MAX_CITY_LENGTH), - ]; - return { streetValidator, houseNoValidator, zipCodValidator, cityValidator }; + return useMemo( + () => [ + getRequiredRule(intl), + getStringsRule(intl, fieldName), + getMaxLengthRule(intl, MAX_CITY_LENGTH), + ], + [intl, fieldName] + ); }; export const usePhoneValidator = () => { const intl = useIntl(); - const phoneValidator = [ - getRequiredRule(intl), - getPhoneRule(intl, invalidPhone), - ]; - return { phoneValidator }; + return useMemo(() => [getRequiredRule(intl), getPhoneRule(intl)], [intl]); }; export const useEmailValidator = () => { const intl = useIntl(); - const emailValidator = [ - getEmailRule(intl, invalidEmail), - getMaxLengthRule(intl, MAX_EMAIL_LENGTH), - ]; - return { emailValidator }; + return useMemo(() => [getEmailRule(intl)], [intl]); }; export const useTableValidator = () => { const intl = useIntl(); - const tableValidator = [getTableNoRule(intl, invalidTableNo)]; - return { tableValidator }; + return useMemo(() => [getTableNoRule(intl)], [intl]); }; diff --git a/services/contact-form/src/constants/errorMessages.js b/services/contact-form/src/constants/errorMessages.js deleted file mode 100644 index c657734..0000000 --- a/services/contact-form/src/constants/errorMessages.js +++ /dev/null @@ -1,27 +0,0 @@ -export const invalidFirstName = { - id: 'contactDataForm.invalid.firstName', -}; -export const invalidLastName = { - id: 'contactDataForm.invalid.lastName', -}; -export const invalidStreet = { - id: 'contactDataForm.invalid.street', -}; -export const invalidZipCode = { - id: 'contactDataForm.invalid.zipCode', -}; -export const invalidCity = { - id: 'contactDataForm.invalid.city', -}; -export const invalidPhone = { - id: 'contactDataForm.invalid.phone', -}; -export const invalidEmail = { - id: 'contactDataForm.invalid.email', -}; -export const invalidHouseNo = { - id: 'contactDataForm.invalid.houseNo', -}; -export const invalidTableNo = { - id: 'contactDataForm.invalid.invalidTableNo', -}; diff --git a/services/contact-form/src/constants/valueLength.js b/services/contact-form/src/constants/valueLength.js index a8667e3..a5d113f 100644 --- a/services/contact-form/src/constants/valueLength.js +++ b/services/contact-form/src/constants/valueLength.js @@ -1,7 +1,7 @@ export const MAX_NAME_LENGTH = 120; export const MAX_PHONE_LENGTH = 20; export const MAX_CITY_LENGTH = 120; -export const MAX_EMAIL_LENGTH = 320; +export const MAX_EMAIL_LENGTH = 255; export const MAX_STREET_LENGTH = 120; export const MAX_POSTAL_CODE_LENGTH = 20; export const MAX_HOUSE_NUMBER_LENGTH = 120; diff --git a/services/contact-form/src/messages/de.json b/services/contact-form/src/messages/de.json index 6fb5b18..1181bff 100644 --- a/services/contact-form/src/messages/de.json +++ b/services/contact-form/src/messages/de.json @@ -42,9 +42,6 @@ "contactDataForm.invalid.invalidTableNo": "Bitte gib eine gültige Tischnummer an.", "contactDataForm.invalid.length": "Eingabe ist zu lang", "contactDataForm.invalid.whiteSpace": "Die Eingabe darf nicht mit einem Whitespace beginnen.", - "contactDataForm.invalid.HouseNo": "Bitte gib eine gültige Hausnummer an.", - "contactDataForm.invalid.Street": "Bitte gib eine gültige Straße an.", - "contactDataForm.invalid.ZipCode": "Die eingegebene Postleitzahl muss 5-stellig sein.", "commitHashVersionDisplay": "luca Kontaktformular", "commitHashVersionDisplay.gitlab": "GitLab" } \ No newline at end of file diff --git a/services/contact-form/src/messages/en.json b/services/contact-form/src/messages/en.json index bcdec26..9edcfb5 100644 --- a/services/contact-form/src/messages/en.json +++ b/services/contact-form/src/messages/en.json @@ -42,9 +42,6 @@ "contactDataForm.invalid.invalidTableNo": "Please enter a valid table number.", "contactDataForm.invalid.length": "Input is too long", "contactDataForm.invalid.whiteSpace": "The input cannot start with a whitespace.", - "contactDataForm.invalid.HouseNo": "Please enter a valid house number.", - "contactDataForm.invalid.Street": "Please enter a valid street.", - "contactDataForm.invalid.ZipCode": "The postcode entered must have 5 digits.", "commitHashVersionDisplay": "luca contact-form", "commitHashVersionDisplay.gitlab": "GitLab" } \ No newline at end of file diff --git a/services/contact-form/src/utils/checkCharacter.js b/services/contact-form/src/utils/checkCharacter.js index 0e22514..def01b3 100644 --- a/services/contact-form/src/utils/checkCharacter.js +++ b/services/contact-form/src/utils/checkCharacter.js @@ -1,2 +1,8 @@ -const SAFE_CHARACTERS_REGEX = /^[\w !&+./:@`|£À-ÿÄăąćĉċÄđēėęěÄğģĥħĩīįİıĵķĸĺļłńņÅőœŗřśÅşšţŦũūÅůűųŵŷźżžơưếệ–-]*$/i; -export const isValidCharacter = value => SAFE_CHARACTERS_REGEX.test(value); +const SAFE_CHARACTERS_REGEX = /^[\w !&()+,./:@`|£À-ÿÄăąćĉċÄđēėęěÄğģĥħĩīįİıĵķĸĺļłńņÅőœŗřśÅşšţŦũūÅůűųŵŷźżžơưếệ–-]*$/i; +const NO_HTTP_REGEX = /^((?!http).)*$/i; +const NO_FTP_REGEX = /^((?!ftp).)*$/i; + +export const isValidCharacter = value => + SAFE_CHARACTERS_REGEX.test(value) && + NO_HTTP_REGEX.test(value) && + NO_FTP_REGEX.test(value); diff --git a/services/contact-form/src/utils/validatorRules.helper.js b/services/contact-form/src/utils/validatorRules.helper.js index dc8d361..96e7c5c 100644 --- a/services/contact-form/src/utils/validatorRules.helper.js +++ b/services/contact-form/src/utils/validatorRules.helper.js @@ -1,19 +1,19 @@ -import { isNumeric, isPostalCode } from 'validator/validator.min'; +import validator from 'validator'; import { isValidCharacter } from './checkCharacter'; import { isValidPhoneNumber } from './checkPhoneNumber'; -export const validateNames = (_, value) => { +export const validateStrings = (_, value) => { if (value?.trim() && !isValidCharacter(value)) { return Promise.reject(); } - if (value?.trim() && isNumeric(value.replace(/\s/g, ''))) { + if (value?.trim() && validator.isNumeric(value.replace(/\s/g, ''))) { return Promise.reject(); } return Promise.resolve(); }; export const validateZipCode = (_, value) => { - if (value?.trim() && !isPostalCode(value, 'DE')) { + if (value?.trim() && !validator.isPostalCode(value, 'DE')) { return Promise.reject(); } return Promise.resolve(); diff --git a/services/contact-form/src/utils/validatorRules.js b/services/contact-form/src/utils/validatorRules.js index d9ad47d..7c7bec5 100644 --- a/services/contact-form/src/utils/validatorRules.js +++ b/services/contact-form/src/utils/validatorRules.js @@ -1,6 +1,6 @@ import { validateHouseNo, - validateNames, + validateStrings, validatePhoneNumber, validateZipCode, } from './validatorRules.helper'; @@ -13,34 +13,34 @@ export const getRequiredRule = intl => ({ }), }); -export const getNamesRule = (intl, message) => ({ - validator: validateNames, - message: intl.formatMessage(message), +export const getStringsRule = (intl, fieldName) => ({ + validator: validateStrings, + message: intl.formatMessage({ id: `contactDataForm.invalid.${fieldName}` }), }); -export const getZipCodeRule = (intl, message) => ({ +export const getZipCodeRule = intl => ({ validator: validateZipCode, - message: intl.formatMessage(message), + message: intl.formatMessage({ id: 'contactDataForm.invalid.zipCode' }), }); -export const getPhoneRule = (intl, message) => ({ +export const getPhoneRule = intl => ({ validator: validatePhoneNumber, - message: intl.formatMessage(message), + message: intl.formatMessage({ id: 'contactDataForm.invalid.phone' }), }); -export const getEmailRule = (intl, message) => ({ +export const getEmailRule = intl => ({ type: 'email', - message: intl.formatMessage(message), + message: intl.formatMessage({ id: 'contactDataForm.invalid.email' }), }); -export const getHouseNoRule = (intl, message) => ({ +export const getHouseNoRule = intl => ({ validator: validateHouseNo, - message: intl.formatMessage(message), + message: intl.formatMessage({ id: 'contactDataForm.invalid.houseNo' }), }); -export const getTableNoRule = (intl, message) => ({ +export const getTableNoRule = intl => ({ type: 'number', - message: intl.formatMessage(message), + message: intl.formatMessage({ id: 'contactDataForm.invalid.invalidTableNo' }), }); export const getMaxLengthRule = (intl, max) => ({ diff --git a/services/health-department/.iyarc b/services/health-department/.iyarc index 1394716..eab85f4 100644 --- a/services/health-department/.iyarc +++ b/services/health-department/.iyarc @@ -17,3 +17,15 @@ # Regular Expression Denial of Service (ReDoS) # This is only used by react-scripts for development purposes. 1755 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32804 +# Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization +# This is only used by react-scripts for development purposes. +1770 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32803 +# Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning +# This is only used by react-scripts for development purposes. +1771 diff --git a/services/health-department/package.json b/services/health-department/package.json index f6de7f8..1fc1dfd 100644 --- a/services/health-department/package.json +++ b/services/health-department/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/health-department", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/health-department/src/ant.css b/services/health-department/src/ant.css index d63d63f..b36d60a 100644 --- a/services/health-department/src/ant.css +++ b/services/health-department/src/ant.css @@ -305,17 +305,47 @@ input:-webkit-autofill:active { } .ant-popover-buttons > .ant-btn { - padding: 0 16px; - border: 1px solid rgb(80, 102, 124); - border-radius: 12px; - color: black; + font-family: Montserrat-Bold, sans-serif; + background: transparent; + border-radius: 24px; + border: 2px solid rgb(80, 102, 124); + cursor: pointer; + height: 32px; + width: 139px; + color: rgba(0, 0, 0, 0.87); + font-size: 14px; + font-weight: bold; + letter-spacing: 0; + line-height: 16px; + text-transform: uppercase; + transition: 0.1s ease-in-out all; +} + +.ant-popover-buttons > .ant-btn:hover { + background: rgb(243, 245, 247); } .ant-popover-buttons > .ant-btn-primary { + font-family: Montserrat-Bold, sans-serif; background: rgb(195, 206, 217); - border-radius: 12px; - border: none; - margin-left: 16px; + border: 2px solid rgb(195, 206, 217); + cursor: pointer; + border-radius: 24px; + height: 32px; + width: auto; + padding-left: 15px; + padding-right: 15px; + color: rgba(0, 0, 0, 0.87); + font-size: 14px; + font-weight: bold; + letter-spacing: 0; + line-height: 16px; + text-transform: uppercase; + transition: 0.1s ease-in-out all; +} +.ant-popover-buttons > .ant-btn-primary:hover { + background: rgb(155, 173, 191); + border-color: rgb(155, 173, 191); } .ant-popover-message > .anticon { diff --git a/services/health-department/src/components/App/App.react.js b/services/health-department/src/components/App/App.react.js index 9bedd02..f9b5e11 100644 --- a/services/health-department/src/components/App/App.react.js +++ b/services/health-department/src/components/App/App.react.js @@ -47,6 +47,8 @@ export const App = () => { if (isProfileLoading) return null; + const { isAdmin } = profileData; + return ( <> <Helmet> @@ -65,9 +67,11 @@ export const App = () => { <ProcessDetails /> </Route> <Route path={TRACKING_ROUTE} component={Tracking} /> - <Route path={USER_MANAGEMENT_ROUTE}> - <UserManagement profileData={profileData} /> - </Route> + {isAdmin && ( + <Route path={USER_MANAGEMENT_ROUTE}> + <UserManagement profileData={profileData} /> + </Route> + )} <Redirect to={`${TRACKING_ROUTE}${window.location.search}`} /> </Switch> </AppWrapper> diff --git a/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.react.js b/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.react.js index aeb503a..ecfa2df 100644 --- a/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.react.js +++ b/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.react.js @@ -4,6 +4,7 @@ import { useDispatch } from 'react-redux'; import { useQueryClient } from 'react-query'; import { push } from 'connected-react-router'; import { notification } from 'antd'; +import { PrimaryButton } from 'components/general'; // API import { logout } from 'network/api'; @@ -12,8 +13,6 @@ import { logout } from 'network/api'; import { LOGIN_ROUTE } from 'constants/routes'; import { clearPrivateKeys } from 'utils/cryptoKeyOperations'; -import { LogoutButtonStyled } from './LogoutButton.styled'; - export const LogoutButton = () => { const intl = useIntl(); const queryClient = useQueryClient(); @@ -38,10 +37,10 @@ export const LogoutButton = () => { .catch(error => console.error(error)); }; return ( - <LogoutButtonStyled data-cy="logout" shape="round" onClick={handleClick}> + <PrimaryButton data-cy="logout" onClick={handleClick}> {intl.formatMessage({ id: 'header.logout', })} - </LogoutButtonStyled> + </PrimaryButton> ); }; diff --git a/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.styled.js b/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.styled.js deleted file mode 100644 index 224c9d8..0000000 --- a/services/health-department/src/components/App/Header/LogoutButton/LogoutButton.styled.js +++ /dev/null @@ -1,11 +0,0 @@ -import styled from 'styled-components'; -import { Button } from 'antd'; - -export const LogoutButtonStyled = styled(Button)` - border: none; - padding: 0 40px; - background-color: rgb(195, 206, 217); - font-family: Montserrat-Bold, sans-serif; - font-size: 14; - font-weight: bold; -`; diff --git a/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.react.js b/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.react.js index 3f810f2..2e41bc8 100644 --- a/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.react.js +++ b/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.react.js @@ -1,6 +1,7 @@ import React, { useCallback } from 'react'; import { useIntl } from 'react-intl'; import { Popconfirm } from 'antd'; +import { PrimaryButton } from 'components/general'; import { useHistory } from 'react-router-dom'; import { TRACKING_ROUTE } from 'constants/routes'; @@ -8,8 +9,6 @@ import { TRACKING_ROUTE } from 'constants/routes'; // Api import { updateProcess } from 'network/api'; -import { ToggleCompletedButton } from './ToggleCompleted.styled'; - const ToggleCompletedRaw = ({ process }) => { const intl = useIntl(); const history = useHistory(); @@ -35,11 +34,11 @@ const ToggleCompletedRaw = ({ process }) => { onConfirm={updateComplete} cancelText={intl.formatMessage({ id: 'toggleComplete.cancel' })} > - <ToggleCompletedButton> + <PrimaryButton data-cy="complete"> {process.isCompleted ? intl.formatMessage({ id: 'processTable.toggleIncomplete' }) : intl.formatMessage({ id: 'processTable.toggleComplete' })} - </ToggleCompletedButton> + </PrimaryButton> </Popconfirm> ); }; diff --git a/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.styled.js b/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.styled.js deleted file mode 100644 index 8c604cf..0000000 --- a/services/health-department/src/components/App/ProcessDetails/HeaderRow/ToggleCompleted/ToggleCompleted.styled.js +++ /dev/null @@ -1,10 +0,0 @@ -import styled from 'styled-components'; -import { Button } from 'antd'; - -export const ToggleCompletedButton = styled(Button)` - border: 2px solid rgb(80, 102, 124); - padding: 0 40px; - font-family: Montserrat-Bold, sans-serif; - font-size: 14px; - font-weight: bold; -`; diff --git a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.react.js b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.react.js index 6f81e72..ec2dce0 100644 --- a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.react.js +++ b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.react.js @@ -3,13 +3,8 @@ import { useIntl } from 'react-intl'; import { Popconfirm } from 'antd'; import moment from 'moment'; -import { - Expiry, - ButtonWrapper, - ContactedButton, - CompletedButton, - ContactButton, -} from './ContactConfirmationButton.styled'; +import { PrimaryButton, SuccessButton } from 'components/general'; +import { Expiry, ButtonWrapper } from './ContactConfirmationButton.styled'; export const ContactConfirmationButton = ({ location, callback }) => { const intl = useIntl(); @@ -24,9 +19,9 @@ export const ContactConfirmationButton = ({ location, callback }) => { if (!isCompleted && !!contactedAt) { return ( <ButtonWrapper> - <ContactedButton> + <PrimaryButton disabled> {intl.formatMessage({ id: 'history.contacted' })} - </ContactedButton> + </PrimaryButton> {renderExpiration()} </ButtonWrapper> ); @@ -35,12 +30,12 @@ export const ContactConfirmationButton = ({ location, callback }) => { if (isCompleted) { return ( <ButtonWrapper> - <CompletedButton + <SuccessButton data-cy={`confirmedLocation_${location.name}`} onClick={() => callback(location)} > {intl.formatMessage({ id: 'history.confirmed' })} - </CompletedButton> + </SuccessButton> {renderExpiration()} </ButtonWrapper> ); @@ -65,7 +60,7 @@ export const ContactConfirmationButton = ({ location, callback }) => { })} > <ButtonWrapper> - <ContactButton + <PrimaryButton disabled={!isCompleted && !!contactedAt} data-cy={`contactLocation_${location.name}`} onClick={() => { @@ -73,7 +68,7 @@ export const ContactConfirmationButton = ({ location, callback }) => { }} > {intl.formatMessage({ id: 'history.contact' })} - </ContactButton> + </PrimaryButton> {renderExpiration()} </ButtonWrapper> </Popconfirm> diff --git a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.styled.js b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.styled.js index cbe41f4..fb4febe 100644 --- a/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.styled.js +++ b/services/health-department/src/components/App/ProcessDetails/History/HistoryTable/ContactConfirmationButton/ContactConfirmationButton.styled.js @@ -1,30 +1,4 @@ import styled from 'styled-components'; -import { Button } from 'antd'; - -const baseStyle = { - padding: '0 40px', - color: 'black', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 14, - fontWeight: 'bold', -}; - -export const ContactButton = styled(Button)({ - ...baseStyle, - backgroundColor: 'rgb(195, 206, 217)', -}); - -export const ContactedButton = styled(Button)({ - ...baseStyle, - backgroundColor: 'rgb(232, 231, 229)', - opacity: 0.5, - cursor: 'not-allowed', -}); - -export const CompletedButton = styled(Button)({ - ...baseStyle, - backgroundColor: 'rgb(211, 222, 195)', -}); export const Expiry = styled.div` margin-top: 8px; diff --git a/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.react.js b/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.react.js index a9ee072..2cbcd6e 100644 --- a/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.react.js +++ b/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.react.js @@ -1,14 +1,11 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Input, Button, Form, notification } from 'antd'; +import { Input, Form, notification } from 'antd'; +import { PrimaryButton } from 'components/general'; import { changePassword } from 'network/api'; import { passwordMeetsCriteria } from 'utils/passwordCheck'; import { handleResponse } from './ChangePasswordView.helper'; -import { - Wrapper, - StyledHeadline, - buttonStyle, -} from './ChangePasswordView.styled'; +import { Wrapper, StyledHeadline } from './ChangePasswordView.styled'; import { inputStyle, StyledButtonRow } from '../Profile.styled'; export const ChangePasswordView = () => { @@ -120,11 +117,11 @@ export const ChangePasswordView = () => { </Form.Item> <StyledButtonRow> <Form.Item> - <Button htmlType="submit" style={buttonStyle}> + <PrimaryButton htmlType="submit"> {intl.formatMessage({ id: 'profile.changePassword', })} - </Button> + </PrimaryButton> </Form.Item> </StyledButtonRow> </Form> diff --git a/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.styled.js b/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.styled.js index 5bb087b..e152450 100644 --- a/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.styled.js +++ b/services/health-department/src/components/App/Profile/ChangePasswordView/ChangePasswordView.styled.js @@ -11,14 +11,3 @@ export const StyledHeadline = styled.div` export const Wrapper = styled.div` width: 65%; `; - -export const buttonStyle = { - backgroundColor: 'transparent', - color: 'rgba(0, 0, 0, 0.87)', - padding: '0 40px', - border: '2px solid rgb(80, 102, 124)', - textTransform: 'uppercase', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 14, - fontWeight: 'bold', -}; diff --git a/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.react.js b/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.react.js index e028eca..9cd6762 100644 --- a/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.react.js +++ b/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.react.js @@ -1,7 +1,7 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { useQuery } from 'react-query'; -import { Button } from 'antd'; +import { PrimaryButton } from 'components/general'; // Api import { getSigningTool } from 'network/api'; @@ -13,7 +13,6 @@ import { StyledHeadline, StyledText, VersionTag, - buttonStyle, } from './DownloadSigningTool.styled'; export const DownloadSigningTool = ({ department }) => { @@ -44,16 +43,12 @@ export const DownloadSigningTool = ({ department }) => { )} </StyledText> <ButtonWrapper> - <Button - href={`${signingTool[0].downloadUrl}`} - target="_blank" - style={buttonStyle} - > + <PrimaryButton href={`${signingTool[0].downloadUrl}`} target="_blank"> {intl.formatMessage( { id: 'profile.signingTool.download.button' }, { version: `v${signingTool[0].version}` } )} - </Button> + </PrimaryButton> <VersionTag> {intl.formatMessage( { id: 'profile.signingTool.download.hash' }, diff --git a/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.styled.js b/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.styled.js index c916245..15a4332 100644 --- a/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.styled.js +++ b/services/health-department/src/components/App/Profile/DownloadSigningTool/DownloadSigningTool.styled.js @@ -33,14 +33,3 @@ export const VersionTag = styled.div` font-size: 14px; font-weight: 600; `; - -export const buttonStyle = { - backgroundColor: '#fdac72', - textTransform: 'uppercase', - color: 'black', - padding: '0 40px', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 14, - fontWeight: 'bold', - marginBottom: 16, -}; diff --git a/services/health-department/src/components/App/Tracking/ManualSearchButton/ManualSearchButton.react.js b/services/health-department/src/components/App/Tracking/ManualSearchButton/ManualSearchButton.react.js index ab5c2d9..76c14b4 100644 --- a/services/health-department/src/components/App/Tracking/ManualSearchButton/ManualSearchButton.react.js +++ b/services/health-department/src/components/App/Tracking/ManualSearchButton/ManualSearchButton.react.js @@ -1,10 +1,9 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button } from 'antd'; import { useModal } from 'components/hooks/useModal'; import { GroupSearchModal } from 'components/App/modals/GroupSearchModal'; -import { buttonStyle } from '../Tracking.styled'; +import { PrimaryButton } from 'components/general'; export const ManualSearchButton = () => { const intl = useIntl(); @@ -20,12 +19,13 @@ export const ManualSearchButton = () => { }; return ( - <Button + <PrimaryButton data-cy="searchGroup" - style={{ ...buttonStyle, marginRight: 24 }} + isButtonWhite + style={{ marginRight: 24 }} onClick={searchLocations} > {intl.formatMessage({ id: 'manualSearch.button' })} - </Button> + </PrimaryButton> ); }; diff --git a/services/health-department/src/components/App/Tracking/NewTrackingButton/NewTrackingButton.react.js b/services/health-department/src/components/App/Tracking/NewTrackingButton/NewTrackingButton.react.js index 6672760..8d1edc5 100644 --- a/services/health-department/src/components/App/Tracking/NewTrackingButton/NewTrackingButton.react.js +++ b/services/health-department/src/components/App/Tracking/NewTrackingButton/NewTrackingButton.react.js @@ -1,13 +1,12 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button } from 'antd'; // Hooks import { useModal } from 'components/hooks/useModal'; // Components import { TrackInfectionModal } from 'components/App/modals/TrackInfectionModal'; -import { buttonStyle } from '../Tracking.styled'; +import { PrimaryButton } from 'components/general'; export const NewTrackingButton = () => { const intl = useIntl(); @@ -23,8 +22,8 @@ export const NewTrackingButton = () => { }; return ( - <Button style={buttonStyle} onClick={trackInfection}> + <PrimaryButton isButtonWhite onClick={trackInfection}> {intl.formatMessage({ id: 'startTracking.button' })} - </Button> + </PrimaryButton> ); }; diff --git a/services/health-department/src/components/App/Tracking/Tracking.styled.js b/services/health-department/src/components/App/Tracking/Tracking.styled.js index 06022ed..70c601f 100644 --- a/services/health-department/src/components/App/Tracking/Tracking.styled.js +++ b/services/health-department/src/components/App/Tracking/Tracking.styled.js @@ -17,10 +17,3 @@ export const VersionFooterWrapper = styled.div` padding-right: 24px; float: right; `; - -export const buttonStyle = { - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 14, - fontWeight: 'bold', - padding: '0 40px', -}; diff --git a/services/health-department/src/components/App/Tracking/TrackingList/Table/EmptyProcesses/EmptyProcesses.react.js b/services/health-department/src/components/App/Tracking/TrackingList/Table/EmptyProcesses/EmptyProcesses.react.js index 50ad00a..bc3a726 100644 --- a/services/health-department/src/components/App/Tracking/TrackingList/Table/EmptyProcesses/EmptyProcesses.react.js +++ b/services/health-department/src/components/App/Tracking/TrackingList/Table/EmptyProcesses/EmptyProcesses.react.js @@ -11,7 +11,7 @@ export const EmptyMessage = styled.div` export const EmptyProcesses = () => { const intl = useIntl(); return ( - <EmptyMessage> + <EmptyMessage data-cy="emptyProcesses"> {intl.formatMessage({ id: 'processTable.empty' })} </EmptyMessage> ); diff --git a/services/health-department/src/components/App/Tracking/TrackingList/Table/Entry/Entry.styled.js b/services/health-department/src/components/App/Tracking/TrackingList/Table/Entry/Entry.styled.js deleted file mode 100644 index 9464c17..0000000 --- a/services/health-department/src/components/App/Tracking/TrackingList/Table/Entry/Entry.styled.js +++ /dev/null @@ -1,7 +0,0 @@ -import { Button } from 'antd'; -import styled from 'styled-components'; - -export const GrowingActionButton = styled(Button)` - background-color: #b8c0ca; - flex-grow: 1; -`; diff --git a/services/health-department/src/components/App/UserManagement/AddEmployeeButton/AddEmployeeButton.react.js b/services/health-department/src/components/App/UserManagement/AddEmployeeButton/AddEmployeeButton.react.js index 1a0fd12..7f9d9b1 100644 --- a/services/health-department/src/components/App/UserManagement/AddEmployeeButton/AddEmployeeButton.react.js +++ b/services/health-department/src/components/App/UserManagement/AddEmployeeButton/AddEmployeeButton.react.js @@ -1,28 +1,20 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button } from 'antd'; // Hooks import { useModal } from 'components/hooks/useModal'; // Components import { AddEmployeeModal } from 'components/App/modals/AddEmployeeModal'; - -const buttonStyles = { - padding: '0 40px', - backgroundColor: 'white', - color: 'black', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 14, - fontWeight: 'bold', -}; +import { PrimaryButton } from 'components/general'; export const AddEmployeeButton = () => { const intl = useIntl(); const [openModal] = useModal(); - const addEmployee = () => { + const addEmployee = event => { + event.currentTarget.blur(); openModal({ title: intl.formatMessage({ id: 'modal.addEmployee.title', @@ -33,10 +25,10 @@ export const AddEmployeeButton = () => { }; return ( - <Button onClick={addEmployee} style={buttonStyles}> + <PrimaryButton isButtonWhite data-cy="addEmployee" onClick={addEmployee}> {intl.formatMessage({ id: 'modal.addEmployee.button', })} - </Button> + </PrimaryButton> ); }; diff --git a/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.react.js b/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.react.js index 1854c24..7eacd29 100644 --- a/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.react.js +++ b/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.react.js @@ -1,6 +1,7 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Popconfirm, notification, Button } from 'antd'; +import { Popconfirm, notification } from 'antd'; +import { PrimaryButton, SecondaryButton } from 'components/general'; import { ReactComponent as CrossSvg } from 'assets/cross.svg'; import { ReactComponent as LockSvg } from 'assets/lock.svg'; @@ -14,12 +15,7 @@ import { deleteEmployee } from 'network/api'; // Components import { RenewEmployeePasswordModal } from 'components/App/modals/RenewEmployeePasswordModal'; -import { - cancelStyle, - buttonStyle, - IconWrapper, - Icon, -} from './EmployeeActions.styled'; +import { IconWrapper, Icon } from './EmployeeActions.styled'; const CrossIcon = () => <Icon component={CrossSvg} />; @@ -106,12 +102,15 @@ export const EmployeeActions = ({ employee, refetch, setEditing, editing }) => { </> ) : ( <> - <Button style={cancelStyle} onClick={() => setEditing(null)}> + <SecondaryButton + style={{ marginRight: 24 }} + onClick={() => setEditing(null)} + > {intl.formatMessage({ id: 'cancel' })} - </Button> - <Button style={buttonStyle} htmlType="submit"> + </SecondaryButton> + <PrimaryButton htmlType="submit"> {intl.formatMessage({ id: 'save' })} - </Button> + </PrimaryButton> </> )} </> diff --git a/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.styled.js b/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.styled.js index 95d31b5..18eb334 100644 --- a/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.styled.js +++ b/services/health-department/src/components/App/UserManagement/EmployeeList/EmployeeActions/EmployeeActions.styled.js @@ -3,25 +3,6 @@ import BaseIcon from '@ant-design/icons'; export const IconWrapper = styled.div``; -const baseStyles = { - fontFamily: 'Montserrat-Bold,sans-serif', - fontSize: 16, - fontWeight: 'bold', - padding: '0 40px', -}; - -export const cancelStyle = { - ...baseStyles, - border: 'none', - boxShadow: 'none', - backgroundColor: 'white', -}; - -export const buttonStyle = { - ...baseStyles, - backgroundColor: 'rgb(195, 206, 217)', -}; - export const Icon = styled(BaseIcon)` color: black; margin: 0 4px; diff --git a/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeForm/AddEmployeeForm.react.js b/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeForm/AddEmployeeForm.react.js index fb42b9d..5c095a8 100644 --- a/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeForm/AddEmployeeForm.react.js +++ b/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeForm/AddEmployeeForm.react.js @@ -1,19 +1,14 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button, Form, Input, notification } from 'antd'; +import { Form, Input, notification } from 'antd'; +import { PrimaryButton, SecondaryButton } from 'components/general'; import { useModal } from 'components/hooks/useModal'; import { createEmployee } from 'network/api'; import { getFormattedPhoneNumber } from 'utils/checkPhoneNumber'; -import { - Wrapper, - ButtonRow, - Info, - buttonStyle, - cancelStyle, -} from '../AddEmployeeModal.styled'; +import { Wrapper, ButtonRow, Info } from '../AddEmployeeModal.styled'; import { getFormElements } from '../AddEmployeeModal.helper'; export const AddEmployeeForm = ({ setNewUserPassword }) => { @@ -67,17 +62,17 @@ export const AddEmployeeForm = ({ setNewUserPassword }) => { ))} <ButtonRow> - <Button style={cancelStyle} onClick={closeModal}> + <SecondaryButton style={{ marginRight: 24 }} onClick={closeModal}> {intl.formatMessage({ id: 'cancel', })} - </Button> + </SecondaryButton> <Form.Item> - <Button htmlType="submit" style={buttonStyle}> + <PrimaryButton htmlType="submit"> {intl.formatMessage({ id: 'userManagement.create.button', })} - </Button> + </PrimaryButton> </Form.Item> </ButtonRow> </Form> diff --git a/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeModal.styled.js b/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeModal.styled.js index cc03062..875f109 100644 --- a/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeModal.styled.js +++ b/services/health-department/src/components/App/modals/AddEmployeeModal/AddEmployeeModal.styled.js @@ -21,24 +21,3 @@ export const Password = styled.div` text-align: center; margin-bottom: 24px; `; - -const baseStyles = { - backgroundColor: 'white', - padding: '0 40px', - color: 'black', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 16, - fontWeight: 'bold', -}; - -export const cancelStyle = { - ...baseStyles, - marginRight: 24, - border: 'none', - boxShadow: 'none', -}; - -export const buttonStyle = { - ...baseStyles, - backgroundColor: 'rgb(195, 206, 217)', -}; diff --git a/services/health-department/src/components/App/modals/AddEmployeeModal/ConfirmPassword/ConfirmPassword.react.js b/services/health-department/src/components/App/modals/AddEmployeeModal/ConfirmPassword/ConfirmPassword.react.js index ad0f143..8669c05 100644 --- a/services/health-department/src/components/App/modals/AddEmployeeModal/ConfirmPassword/ConfirmPassword.react.js +++ b/services/health-department/src/components/App/modals/AddEmployeeModal/ConfirmPassword/ConfirmPassword.react.js @@ -1,17 +1,12 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { useQueryClient } from 'react-query'; -import { Button, Popconfirm } from 'antd'; +import { Popconfirm } from 'antd'; +import { PrimaryButton } from 'components/general'; import { useModal } from 'components/hooks/useModal'; -import { - Wrapper, - ButtonRow, - Info, - Password, - buttonStyle, -} from '../AddEmployeeModal.styled'; +import { Wrapper, ButtonRow, Info, Password } from '../AddEmployeeModal.styled'; export const ConfirmPassword = ({ password }) => { const intl = useIntl(); @@ -30,7 +25,7 @@ export const ConfirmPassword = ({ password }) => { id: 'userManagement.created.info', })} </Info> - <Password>{password}</Password> + <Password data-cy="generatedPassword">{password}</Password> <ButtonRow> <Popconfirm placement="top" @@ -45,11 +40,11 @@ export const ConfirmPassword = ({ password }) => { id: 'userManagement.created.confirm.cancel', })} > - <Button style={buttonStyle}> + <PrimaryButton> {intl.formatMessage({ id: 'userManagement.created.button', })} - </Button> + </PrimaryButton> </Popconfirm> </ButtonRow> </Wrapper> diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.react.js b/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.react.js index ffdfc3c..205d12e 100644 --- a/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.react.js +++ b/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.react.js @@ -3,6 +3,7 @@ import moment from 'moment'; import { useIntl } from 'react-intl'; import { useQueryClient } from 'react-query'; import { Form, DatePicker, TimePicker, notification } from 'antd'; +import { PrimaryButton, SecondaryButton } from 'components/general'; import { createLocationTransfer } from 'network/api'; import { mergeTimeAndDateObject } from 'utils/moment'; @@ -13,9 +14,7 @@ import { InfoText, AddressText, DateText, - OpenProcessButton, DateSelectorWrapper, - BackButton, DatePickerRow, } from './DataRequestModal.styled'; @@ -182,13 +181,13 @@ export const DataRequestModal = ({ group, back }) => { </DateSelectorWrapper> <ButtonRow> - <BackButton onClick={back}> + <SecondaryButton onClick={back}> {intl.formatMessage({ id: 'modal.dataRequest.back' })} - </BackButton> + </SecondaryButton> <Form.Item> - <OpenProcessButton htmlType="submit" data-cy="requestGroupData"> + <PrimaryButton htmlType="submit" data-cy="requestGroupData"> {intl.formatMessage({ id: 'modal.dataRequest.button' })} - </OpenProcessButton> + </PrimaryButton> </Form.Item> </ButtonRow> </Form> diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.styled.js b/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.styled.js index 848ea6b..55423f5 100644 --- a/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.styled.js +++ b/services/health-department/src/components/App/modals/GroupSearchModal/DataRequestModal/DataRequestModal.styled.js @@ -1,4 +1,3 @@ -import { Button } from 'antd'; import styled from 'styled-components'; export const DatePickerRow = styled.div` @@ -12,25 +11,6 @@ export const ButtonRow = styled.div` margin-top: 24px; `; -export const BackButton = styled(Button)` - border: 2px solid rgb(80, 102, 124); - color: rgba(0, 0, 0, 0.87); - background: transparent; - font-weight: bold; - text-transform: uppercase; - height: 40px; - padding: 0 24px; -`; - -export const OpenProcessButton = styled(Button)` - color: rgba(0, 0, 0, 0.87); - background-color: rgb(195, 206, 217); - font-weight: bold; - text-transform: uppercase; - height: 40px; - padding: 0 24px; -`; - export const GroupText = styled.div` margin-bottom: 8px; font-size: 16px; diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.react.js b/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.react.js index 269fb09..0fbdc49 100644 --- a/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.react.js +++ b/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.react.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useIntl } from 'react-intl'; import { Form } from 'antd'; +import { PrimaryButton } from 'components/general'; // API import { findGroups } from 'network/api'; @@ -13,20 +14,14 @@ import { validateZipCode } from 'utils/validators.helper'; // Components import { DataRequestModal } from 'components/App/modals/GroupSearchModal/DataRequestModal'; -import { EmptySearch } from './EmptySearch'; +import { SearchResults } from './SearchResults'; import { StyledSearchOutlined, DescriptionText, GroupSearchInput, ZipCodeInput, - SubmitButton, InputWrapper, GroupSearchWrapper, - ResultsWrapper, - Entry, - EntryInfo, - EntryAdress, - EntryName, } from './GroupSearchModal.styled'; export const GroupSearchModal = () => { @@ -132,39 +127,22 @@ export const GroupSearchModal = () => { </Form.Item> </InputWrapper> <Form.Item> - <SubmitButton + <PrimaryButton data-cy="startGroupSearch" htmlType="submit" + floatRight disabled={!inputValid.zipCode || !inputValid.groupName} > {intl.formatMessage({ id: 'groupSearch.form.button.search', })} - </SubmitButton> + </PrimaryButton> </Form.Item> </Form> - {!!searchResults && ( - <ResultsWrapper> - {searchResults.length > 0 ? ( - searchResults.map(entry => ( - <Entry - key={entry.groupId} - data-cy={`group_${entry.name}`} - onClick={() => setRequestData(entry)} - > - <EntryInfo> - <EntryName>{entry.name}</EntryName> - <EntryAdress> - {`${entry.baseLocation.streetName} ${entry.baseLocation.streetNr}, ${entry.baseLocation.zipCode} ${entry.baseLocation.city}`} - </EntryAdress> - </EntryInfo> - </Entry> - )) - ) : ( - <EmptySearch /> - )} - </ResultsWrapper> - )} + <SearchResults + setRequestData={setRequestData} + searchResults={searchResults} + /> </GroupSearchWrapper> </> ); diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.styled.js b/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.styled.js index 952d8f1..3cd170b 100644 --- a/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.styled.js +++ b/services/health-department/src/components/App/modals/GroupSearchModal/GroupSearchModal.styled.js @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { Button, Input } from 'antd'; +import { Input } from 'antd'; import { SearchOutlined } from '@ant-design/icons'; export const StyledSearchOutlined = styled(SearchOutlined)` @@ -25,16 +25,6 @@ export const ZipCodeInput = styled(Input)` border-radius: 0; `; -export const SubmitButton = styled(Button)` - color: rgba(0, 0, 0, 0.87); - background-color: rgb(195, 206, 217); - font-weight: bold; - height: 40px; - float: right; - text-transform: uppercase; - padding: 0 24px; -`; - export const GroupSearchWrapper = styled.div` width: 100%; background-color: #ffffff; @@ -45,37 +35,3 @@ export const InputWrapper = styled.div` display: flex; justify-content: space-between; `; - -export const ResultsWrapper = styled.div` - display: flex; - flex-direction: column; - max-height: 500px; - margin-top: 24px; - overflow-y: auto; -`; - -export const Entry = styled.div` - display: flex; - justify-content: space-between; - padding-top: 16px; - border-bottom: 1px solid rgba(151, 151, 151, 0.5); - - &:hover { - background-color: rgb(243, 243, 243); - cursor: pointer; - } -`; - -export const EntryInfo = styled.div` - display: flex; - flex-direction: column; -`; - -export const EntryName = styled.div` - font-weight: bold; - margin-bottom: 8px; -`; - -export const EntryAdress = styled.div` - margin-bottom: 8px; -`; diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/EmptySearch/EmptySearch.react.js b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/EmptySearch/EmptySearch.react.js similarity index 100% rename from services/health-department/src/components/App/modals/GroupSearchModal/EmptySearch/EmptySearch.react.js rename to services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/EmptySearch/EmptySearch.react.js diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/EmptySearch/index.js b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/EmptySearch/index.js similarity index 100% rename from services/health-department/src/components/App/modals/GroupSearchModal/EmptySearch/index.js rename to services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/EmptySearch/index.js diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.react.js b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.react.js new file mode 100644 index 0000000..d8c90d8 --- /dev/null +++ b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.react.js @@ -0,0 +1,40 @@ +import React from 'react'; + +// Components +import { EmptySearch } from './EmptySearch'; +import { + ResultsWrapper, + Entry, + EntryInfo, + EntryAdress, + EntryName, +} from './SearchResults.styled'; + +export const SearchResults = ({ setRequestData, searchResults }) => { + return ( + <> + {!!searchResults && ( + <ResultsWrapper> + {searchResults.length > 0 ? ( + searchResults.map(entry => ( + <Entry + key={entry.groupId} + data-cy={`group_${entry.name}`} + onClick={() => setRequestData(entry)} + > + <EntryInfo> + <EntryName>{entry.name}</EntryName> + <EntryAdress> + {`${entry.baseLocation.streetName} ${entry.baseLocation.streetNr}, ${entry.baseLocation.zipCode} ${entry.baseLocation.city}`} + </EntryAdress> + </EntryInfo> + </Entry> + )) + ) : ( + <EmptySearch /> + )} + </ResultsWrapper> + )} + </> + ); +}; diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.styled.js b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.styled.js new file mode 100644 index 0000000..02c50cd --- /dev/null +++ b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/SearchResults.styled.js @@ -0,0 +1,35 @@ +import styled from 'styled-components'; + +export const ResultsWrapper = styled.div` + display: flex; + flex-direction: column; + max-height: 500px; + margin-top: 24px; + overflow-y: auto; +`; + +export const Entry = styled.div` + display: flex; + justify-content: space-between; + padding: 16px 0 0 16px; + border-bottom: 1px solid rgba(151, 151, 151, 0.5); + + &:hover { + background-color: rgb(243, 243, 243); + cursor: pointer; + } +`; + +export const EntryInfo = styled.div` + display: flex; + flex-direction: column; +`; + +export const EntryName = styled.div` + font-weight: bold; + margin-bottom: 8px; +`; + +export const EntryAdress = styled.div` + margin-bottom: 8px; +`; diff --git a/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/index.js b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/index.js new file mode 100644 index 0000000..c76bf4c --- /dev/null +++ b/services/health-department/src/components/App/modals/GroupSearchModal/SearchResults/index.js @@ -0,0 +1 @@ +export { SearchResults } from './SearchResults.react'; diff --git a/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/CompleteStep/CompleteStep.react.js b/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/CompleteStep/CompleteStep.react.js index 3dd803f..4661d18 100644 --- a/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/CompleteStep/CompleteStep.react.js +++ b/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/CompleteStep/CompleteStep.react.js @@ -1,6 +1,6 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button } from 'antd'; +import { PrimaryButton } from 'components/general'; // Components import { Explain, ButtonRow } from '../RegisterHealthDepartmentModal.styled'; @@ -21,19 +21,11 @@ export const CompleteStep = ({ closeModal }) => { marginTop: 200, }} > - <Button - data-cy="finish" - onClick={closeModal} - style={{ - backgroundColor: '#4e6180', - padding: '0 40px', - color: 'white', - }} - > + <PrimaryButton data-cy="finish" onClick={closeModal}> {intl.formatMessage({ id: 'modal.registerHealthDepartment.step1.button', })} - </Button> + </PrimaryButton> </ButtonRow> </> ); diff --git a/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/GenerateKeysStep/GenerateKeysStep.react.js b/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/GenerateKeysStep/GenerateKeysStep.react.js index e192a4a..65bd56f 100644 --- a/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/GenerateKeysStep/GenerateKeysStep.react.js +++ b/services/health-department/src/components/App/modals/RegisterHealthDepartmentModal/GenerateKeysStep/GenerateKeysStep.react.js @@ -1,8 +1,8 @@ import React, { useState } from 'react'; import FileSaver from 'file-saver'; import { useIntl } from 'react-intl'; -import { Button } from 'antd'; import { FileProtectOutlined } from '@ant-design/icons'; +import { PrimaryButton } from 'components/general'; import { bytesToHex } from '@lucaapp/crypto'; import { generatePrivateKeyFile } from 'utils/privateKey'; @@ -56,20 +56,17 @@ export const GenerateKeysStep = ({ </Explain> <DownloadRow> <FileProtectOutlined style={{ fontSize: 40, marginBottom: 24 }} /> - <Button + <PrimaryButton data-cy="downloadPrivateKey" onClick={onDownLoad} style={{ - padding: '0 40px', - color: 'black', - backgroundColor: '#b8c0ca', width: '50%', }} > {intl.formatMessage({ id: 'modal.registerHealthDepartment.step0.downloadButton', })} - </Button> + </PrimaryButton> </DownloadRow> <ButtonRow numberOfButtons={1} @@ -77,20 +74,15 @@ export const GenerateKeysStep = ({ marginTop: 40, }} > - <Button + <PrimaryButton data-cy="next" onClick={proceed} disabled={!hasDownloaded} - style={{ - backgroundColor: '#4e6180', - padding: '0 40px', - color: 'white', - }} > {intl.formatMessage({ id: 'modal.registerHealthDepartment.step0.nextButton', })} - </Button> + </PrimaryButton> </ButtonRow> </> ); diff --git a/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/ConfirmNewPassword/ConfirmNewPassword.react.js b/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/ConfirmNewPassword/ConfirmNewPassword.react.js index 62f614f..d0c4a04 100644 --- a/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/ConfirmNewPassword/ConfirmNewPassword.react.js +++ b/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/ConfirmNewPassword/ConfirmNewPassword.react.js @@ -1,6 +1,7 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button, Popconfirm } from 'antd'; +import { Popconfirm } from 'antd'; +import { PrimaryButton } from 'components/general'; import { useModal } from 'components/hooks/useModal'; @@ -9,7 +10,6 @@ import { ButtonRow, Info, Password, - confirmStyle, } from '../RenewEmployeePasswordModal.styled'; export const ConfirmNewPassword = ({ password }) => { @@ -42,11 +42,11 @@ export const ConfirmNewPassword = ({ password }) => { id: 'cancel', })} > - <Button style={confirmStyle}> + <PrimaryButton> {intl.formatMessage({ id: 'modal.renewEmployeePassword.done', })} - </Button> + </PrimaryButton> </Popconfirm> </ButtonRow> </Wrapper> diff --git a/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/CreateNewPassword/CreateNewPassword.react.js b/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/CreateNewPassword/CreateNewPassword.react.js index 7595fbb..f8c2cdf 100644 --- a/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/CreateNewPassword/CreateNewPassword.react.js +++ b/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/CreateNewPassword/CreateNewPassword.react.js @@ -1,18 +1,13 @@ import React from 'react'; import { useIntl } from 'react-intl'; -import { Button, notification, Popconfirm } from 'antd'; +import { notification, Popconfirm } from 'antd'; +import { PrimaryButton, SecondaryButton } from 'components/general'; import { renewEmployeePassword } from 'network/api'; import { useModal } from 'components/hooks/useModal'; -import { - Wrapper, - ButtonRow, - Info, - cancelStyle, - confirmStyle, -} from '../RenewEmployeePasswordModal.styled'; +import { Wrapper, ButtonRow, Info } from '../RenewEmployeePasswordModal.styled'; export const CreateNewPassword = ({ employee, setNewUserPassword }) => { const intl = useIntl(); @@ -59,11 +54,11 @@ export const CreateNewPassword = ({ employee, setNewUserPassword }) => { )} </Info> <ButtonRow> - <Button style={cancelStyle} onClick={close}> + <SecondaryButton style={{ marginRight: 24 }} onClick={close}> {intl.formatMessage({ id: 'cancel', })} - </Button> + </SecondaryButton> <Popconfirm placement="top" title={intl.formatMessage({ @@ -77,11 +72,11 @@ export const CreateNewPassword = ({ employee, setNewUserPassword }) => { id: 'cancel', })} > - <Button style={confirmStyle}> + <PrimaryButton> {intl.formatMessage({ id: 'modal.renewEmployeePassword.create', })} - </Button> + </PrimaryButton> </Popconfirm> </ButtonRow> </Wrapper> diff --git a/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/RenewEmployeePasswordModal.styled.js b/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/RenewEmployeePasswordModal.styled.js index e58d0cb..b5c3668 100644 --- a/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/RenewEmployeePasswordModal.styled.js +++ b/services/health-department/src/components/App/modals/RenewEmployeePasswordModal/RenewEmployeePasswordModal.styled.js @@ -21,24 +21,3 @@ export const Password = styled.div` text-align: center; margin-bottom: 24px; `; - -const baseStyles = { - backgroundColor: 'white', - padding: '0 40px', - color: 'black', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 16, - fontWeight: 'bold', -}; - -export const cancelStyle = { - ...baseStyles, - marginRight: 24, - border: 'none', - boxShadow: 'none', -}; - -export const confirmStyle = { - ...baseStyles, - background: 'rgb(195, 206, 217)', -}; diff --git a/services/health-department/src/components/App/modals/SormasModal/CredentialsStep/CredentialsStep.react.js b/services/health-department/src/components/App/modals/SormasModal/CredentialsStep/CredentialsStep.react.js index 2ece020..844c0fa 100644 --- a/services/health-department/src/components/App/modals/SormasModal/CredentialsStep/CredentialsStep.react.js +++ b/services/health-department/src/components/App/modals/SormasModal/CredentialsStep/CredentialsStep.react.js @@ -1,7 +1,8 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { getSormasClient } from 'network/sormas'; -import { Button, Form, Input, notification } from 'antd'; +import { Form, Input, notification } from 'antd'; +import { PrimaryButton } from 'components/general'; import { ButtonWrapper } from '../SormasModal.styled'; @@ -60,11 +61,11 @@ export function CredentialsStep({ onFinish }) { ))} <ButtonWrapper> <Form.Item> - <Button htmlType="submit"> + <PrimaryButton htmlType="submit"> {intl.formatMessage({ id: 'modal.sormas.credentialstep.connect', })} - </Button> + </PrimaryButton> </Form.Item> </ButtonWrapper> </Form> diff --git a/services/health-department/src/components/App/modals/SormasModal/SelectCaseStep/SelectCaseStep.react.js b/services/health-department/src/components/App/modals/SormasModal/SelectCaseStep/SelectCaseStep.react.js index 73ce5c4..16e31a2 100644 --- a/services/health-department/src/components/App/modals/SormasModal/SelectCaseStep/SelectCaseStep.react.js +++ b/services/health-department/src/components/App/modals/SormasModal/SelectCaseStep/SelectCaseStep.react.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { Select, Button } from 'antd'; +import { Select } from 'antd'; +import { PrimaryButton } from 'components/general'; import { useQuery } from 'react-query'; import { useIntl } from 'react-intl'; @@ -59,7 +60,7 @@ export function SelectCaseStep({ client, onSelectCase }) { </ResultWrapper> )} <ButtonWrapper> - <Button + <PrimaryButton style={{ marginTop: '12px', alignSelf: 'flex-end' }} disabled={!selectedCase} onClick={() => { @@ -69,7 +70,7 @@ export function SelectCaseStep({ client, onSelectCase }) { {intl.formatMessage({ id: 'modal.sormas.selectstep.export', })} - </Button> + </PrimaryButton> </ButtonWrapper> </> ); diff --git a/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.react.js b/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.react.js index 51e6176..efe6be9 100644 --- a/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.react.js +++ b/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.react.js @@ -1,7 +1,8 @@ import React, { useState, useRef } from 'react'; import { useIntl } from 'react-intl'; import { useQueryClient } from 'react-query'; -import { Form, Button, notification, Popconfirm } from 'antd'; +import { Form, notification, Popconfirm } from 'antd'; +import { PrimaryButton } from 'components/general'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { @@ -23,7 +24,6 @@ import { ItemWrapper, StyledInput, StyledFormItem, - buttonStyle, } from './TrackInfectionModal.styled'; import { getTanRules, TAN_SECTION_LENGTH } from './InfectionModal.helper'; @@ -210,9 +210,9 @@ export const TrackInfectionModal = () => { })} icon={<QuestionCircleOutlined style={{ color: 'red' }} />} > - <Button style={buttonStyle} loading={isLoading}> + <PrimaryButton loading={isLoading}> {intl.formatMessage({ id: 'modal.trackInfection.button' })} - </Button> + </PrimaryButton> </Popconfirm> </Form.Item> </SubmitWrapper> diff --git a/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.styled.js b/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.styled.js index 6d7f385..3a3fc7a 100644 --- a/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.styled.js +++ b/services/health-department/src/components/App/modals/TrackInfectionModal/TrackInfectionModal.styled.js @@ -42,11 +42,3 @@ export const StyledFormItem = styled(Form.Item)` width: auto !important; max-width: 160px; `; - -export const buttonStyle = { - background: 'rgb(195, 206, 217)', - fontFamily: 'Montserrat-Bold, sans-serif', - fontSize: 14, - fontWeight: 'bold', - padding: '0 40px', -}; diff --git a/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.react.js b/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.react.js index 82064d4..fe6f581 100644 --- a/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.react.js +++ b/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.react.js @@ -1,7 +1,8 @@ import React, { useRef } from 'react'; import { useIntl } from 'react-intl'; import { useQuery } from 'react-query'; -import { notification, Button } from 'antd'; +import { notification } from 'antd'; +import { PrimaryButton } from 'components/general'; import { base64ToHex, EC_KEYPAIR_FROM_PRIVATE_KEY } from '@lucaapp/crypto'; @@ -12,16 +13,12 @@ import { parsePrivateKeyFile } from 'utils/privateKey'; import { MAX_PRIVATE_KEY_FILE_SIZE } from 'constants/valueLength'; import { storeHealthDepartmentPrivateKeys } from 'utils/cryptoKeyOperations'; -import { - UploadButton, - HiddenUpload, - Info, - ButtonRow, -} from './UploadKeyFileModal.styled'; +import { HiddenUpload, Info, ButtonRow } from './UploadKeyFileModal.styled'; export const UploadKeyFileModal = ({ keysData, onFinish }) => { const intl = useIntl(); const reader = useRef(new FileReader()); + const uploadReference = useRef(null); const [, closeModal] = useModal(); const { isLoading, data: privateKeySecret } = useQuery( 'privateKeySecret', @@ -79,6 +76,12 @@ export const UploadKeyFileModal = ({ keysData, onFinish }) => { reader.current.readAsText(keyFile); }; + const triggerInput = () => { + if (uploadReference?.current) { + uploadReference.current.click(); + } + }; + if (isLoading) { return null; } @@ -91,25 +94,18 @@ export const UploadKeyFileModal = ({ keysData, onFinish }) => { })} </Info> <HiddenUpload + ref={uploadReference} type="file" accept=".luca" onChange={onFile} data-testid="fileUpload" /> <ButtonRow> - <Button - style={{ - backgroundColor: '#4e6180', - padding: '0 40px', - color: 'white', - }} - > - <UploadButton href="#"> - {intl.formatMessage({ - id: 'modal.uploadKeyModal.button', - })} - </UploadButton> - </Button> + <PrimaryButton onClick={triggerInput} style={{ zIndex: 3 }}> + {intl.formatMessage({ + id: 'modal.uploadKeyModal.button', + })} + </PrimaryButton> </ButtonRow> </> ); diff --git a/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.styled.js b/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.styled.js index 7ae478a..64afd89 100644 --- a/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.styled.js +++ b/services/health-department/src/components/App/modals/UploadKeyFileModal/UploadKeyFileModal.styled.js @@ -1,9 +1,5 @@ import styled from 'styled-components'; -export const UploadButton = styled.span` - cursor: pointer; -`; - export const Info = styled.div` margin-bottom: 24px; `; @@ -17,11 +13,8 @@ export const HiddenUpload = styled.input` position: absolute; top: 0; left: 0; - height: 100%; width: 100%; - opacity: 0; - z-index: 2; `; diff --git a/services/health-department/src/components/Login/Login.react.js b/services/health-department/src/components/Login/Login.react.js index d5e2115..e380ca7 100644 --- a/services/health-department/src/components/Login/Login.react.js +++ b/services/health-department/src/components/Login/Login.react.js @@ -1,7 +1,8 @@ import React, { useState } from 'react'; import { Helmet } from 'react-helmet'; import { useIntl } from 'react-intl'; -import { Form, Input, Button } from 'antd'; +import { Form, Input } from 'antd'; +import { PrimaryButton } from 'components/general'; import { useDispatch } from 'react-redux'; import { push } from 'connected-react-router'; import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons'; @@ -15,7 +16,6 @@ import { login } from 'network/api'; // Constants import { APP_ROUTE } from 'constants/routes'; -import { IS_MOBILE } from 'constants/environment'; // Components import { Footer } from './Footer'; @@ -129,20 +129,20 @@ export const Login = () => { <Form.Item style={{ marginBottom: 0, + marginTop: 24, }} > - <Button + <PrimaryButton htmlType="submit" - size="large" + isButtonWhite style={{ - padding: IS_MOBILE ? '' : '0 80px', marginTop: '24px', }} > {intl.formatMessage({ id: 'login.form.button', })} - </Button> + </PrimaryButton> </Form.Item> </ButtonWrapper> </Form> diff --git a/services/health-department/src/components/general/Buttons.styled.js b/services/health-department/src/components/general/Buttons.styled.js new file mode 100644 index 0000000..2e49913 --- /dev/null +++ b/services/health-department/src/components/general/Buttons.styled.js @@ -0,0 +1,86 @@ +import styled from 'styled-components'; +import { Button } from 'antd'; + +const ButtonGeneral = { + fontFamily: 'Montserrat-Bold, sans-serif', + fontSize: '14px', + fontWeight: 'bold', + letterSpacing: 0, + lineHeight: '16px', + textTransform: 'uppercase', + height: '32px', + minWidth: '139px', + width: 'auto', + paddingLeft: '40px', + paddingRight: '40px', + boxShadow: 'none', +}; + +export const PrimaryButton = styled(Button)` + ${{ ...ButtonGeneral }} + background: ${({ isButtonWhite }) => + isButtonWhite ? '#fff' : 'rgb(195, 206, 217)'}; + border: 2px solid + ${({ isButtonWhite }) => (isButtonWhite ? '#fff' : 'rgb(195, 206, 217)')}; + border-radius: 24px; + color: rgba(0, 0, 0, 0.87); + cursor: pointer; + transition: 0.1s ease-in-out all; + ${({ floatRight }) => (floatRight ? 'float:right' : '')}; + + &:hover, + &:focus { + color: rgba(0, 0, 0, 0.87) !important; + background: rgb(155, 173, 191) !important; + border-color: rgb(155, 173, 191) !important; + } + + &:disabled { + background: rgb(218, 224, 231) !important; + border-color: rgb(218, 224, 231) !important; + color: rgb(0 0 0 / 50%) !important; + cursor: no-drop; + } +`; + +export const SecondaryButton = styled(Button)` + ${{ ...ButtonGeneral }} + color: rgba(0, 0, 0, 0.87); + background: transparent; + border-radius: 24px; + border: 2px solid rgb(80, 102, 124); + cursor: pointer; + transition: 0.1s ease-in-out all; + + &:hover, + &:focus { + color: rgba(0, 0, 0, 0.87); + background: rgb(218, 224, 231); + border: 2px solid rgb(80, 102, 124); + } + + &:disabled { + color: rgba(0, 0, 0, 0.25); + background: #f5f5f5; + border-color: #d9d9d9; + text-shadow: none; + box-shadow: none; + cursor: no-drop; + } +`; + +export const SuccessButton = styled(Button)` + ${{ ...ButtonGeneral }} + color: rgba(0, 0, 0, 0.87); + background: rgb(211, 222, 195); + border: 2px solid rgb(211, 222, 195); + border-radius: 24px; + cursor: pointer; + transition: 0.1s ease-in-out all; + &:hover, + &:focus { + color: rgba(0, 0, 0, 0.87) !important; + background: rgb(178, 197, 150) !important; + border-color: rgb(178, 197, 150) !important; + } +`; diff --git a/services/health-department/src/components/general/index.js b/services/health-department/src/components/general/index.js new file mode 100644 index 0000000..0ffeb4b --- /dev/null +++ b/services/health-department/src/components/general/index.js @@ -0,0 +1,5 @@ +export { + PrimaryButton, + SecondaryButton, + SuccessButton, +} from './Buttons.styled'; diff --git a/services/health-department/src/utils/sanitizer.test.js b/services/health-department/src/utils/sanitizer.test.js index be8650b..58bc463 100644 --- a/services/health-department/src/utils/sanitizer.test.js +++ b/services/health-department/src/utils/sanitizer.test.js @@ -111,3 +111,16 @@ it('handles other input', () => { expect(sanitizeForCSV(-1)).toBe('-1'); expect(sanitizeForCSV(null)).toBe(''); }); + +it('handles brackets and commas', () => { + const testData = { + '(),': '_', + 'Frankfurt (Oder)': 'Frankfurt Oder ', + 'C,S,V': 'C S V', + '(test), test, test': '_test test test', + }; + + Object.keys(testData).forEach(value => { + expect(sanitizeForCSV(value)).toBe(testData[value]); + }); +}); diff --git a/services/locations/.iyarc b/services/locations/.iyarc index feae8f8..1c063e4 100644 --- a/services/locations/.iyarc +++ b/services/locations/.iyarc @@ -9,3 +9,15 @@ # can only be leveraged during source map parsing # there is currently no version of stylelint which supports a fixed version of postcss 1693 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32804 +# Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization +# This is only used by react-scripts for development purposes. +1770 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32803 +# Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning +# This is only used by react-scripts for development purposes. +1771 \ No newline at end of file diff --git a/services/locations/package.json b/services/locations/package.json index adb303c..bfa9a1e 100644 --- a/services/locations/package.json +++ b/services/locations/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/locations", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/locations/src/assets/verification.svg b/services/locations/src/assets/verification.svg new file mode 100644 index 0000000..b484b3d --- /dev/null +++ b/services/locations/src/assets/verification.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>Icon / Verifizierung</title> + <g id="Gesundheitsamt" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Profil" transform="translate(-299.000000, -56.000000)"> + <g id="Group" transform="translate(299.000000, 56.000000)"> + <polygon id="Star" fill="#7CA5F6" points="8 14.4 4.93853254 15.3910363 3.4745166 12.5254834 0.60896374 11.0614675 1.6 8 0.60896374 4.93853254 3.4745166 3.4745166 4.93853254 0.60896374 8 1.6 11.0614675 0.60896374 12.5254834 3.4745166 15.3910363 4.93853254 14.4 8 15.3910363 11.0614675 12.5254834 12.5254834 11.0614675 15.3910363"></polygon> + <polyline id="Path-3" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round" points="5 9 7 11 11 5"></polyline> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/services/locations/src/assets/warning.svg b/services/locations/src/assets/warning.svg new file mode 100644 index 0000000..d7c53b0 --- /dev/null +++ b/services/locations/src/assets/warning.svg @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="16px" height="15px" viewBox="0 0 16 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>Icon / 16px / Warning</title> + <g id="Datenfreigabe" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="PrivatenSchlüssel_Bestätigung-/->72h" transform="translate(-324.000000, -478.000000)"> + <g id="Group" transform="translate(324.000000, 477.000000)"> + <path d="M8.89442719,1.78885438 L15.2763932,14.5527864 C15.5233825,15.0467649 15.3231581,15.6474379 14.8291796,15.8944272 C14.6903242,15.9638549 14.5372111,16 14.381966,16 L1.61803399,16 C1.06574924,16 0.618033989,15.5522847 0.618033989,15 C0.618033989,14.8447549 0.654179081,14.6916418 0.723606798,14.5527864 L7.10557281,1.78885438 C7.35256206,1.29487588 7.9532351,1.09465154 8.4472136,1.34164079 C8.640741,1.43840449 8.79766349,1.59532698 8.89442719,1.78885438 Z" id="Triangle" fill="#F16704"></path> + <line x1="8" y1="6" x2="8" y2="10" id="Path" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round"></line> + <circle id="Oval" fill="#FFFFFF" cx="8" cy="13" r="1"></circle> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/services/locations/src/components/App/LocationFooter/LocationFooter.react.js b/services/locations/src/components/App/LocationFooter/LocationFooter.react.js index 1b4049f..41878ca 100644 --- a/services/locations/src/components/App/LocationFooter/LocationFooter.react.js +++ b/services/locations/src/components/App/LocationFooter/LocationFooter.react.js @@ -13,7 +13,7 @@ import { Link, Version, Wrapper } from './LocationFooter.styled'; export function LocationFooter({ color = '#000', - title = 'location', + title = 'Locations', showBadgePrivacy = false, }) { const intl = useIntl(); diff --git a/services/locations/src/components/App/modals/GuestListModal/GuestListModal.react.js b/services/locations/src/components/App/modals/GuestListModal/GuestListModal.react.js index b452379..86356f7 100644 --- a/services/locations/src/components/App/modals/GuestListModal/GuestListModal.react.js +++ b/services/locations/src/components/App/modals/GuestListModal/GuestListModal.react.js @@ -76,7 +76,11 @@ export const GuestListModal = ({ location }) => { title: intl.formatMessage({ id: 'modal.guestList.checkinDate' }), key: 'checkinDate', render: function renderCheckinDate(trace) { - return <>{moment.unix(trace.checkin).format('DD.MM.YYYY')}</>; + return ( + <div data-cy="checkinDate"> + {moment.unix(trace.checkin).format('DD.MM.YYYY')} + </div> + ); }, }, { @@ -84,7 +88,7 @@ export const GuestListModal = ({ location }) => { key: 'checkinTime', render: function renderCheckinTime(trace) { return ( - <div data-cy="trackingTime"> + <div data-cy="checkinTime"> {moment.unix(trace.checkin).format('HH:mm')} </div> ); @@ -95,11 +99,11 @@ export const GuestListModal = ({ location }) => { key: 'checkoutDate', render: function renderCheckinDate(trace) { return ( - <> + <div data-cy="checkoutDate"> {trace.checkout ? moment.unix(trace.checkout).format('DD.MM.YYYY') : '-'} - </> + </div> ); }, }, @@ -108,9 +112,9 @@ export const GuestListModal = ({ location }) => { key: 'checkoutTime', render: function renderCheckinTime(trace) { return ( - <> + <div data-cy="checkoutTime"> {trace.checkout ? moment.unix(trace.checkout).format('HH:mm') : '-'} - </> + </div> ); }, }, diff --git a/services/locations/src/components/ShareData/ShareData.styled.js b/services/locations/src/components/ShareData/ShareData.styled.js index bd1e202..995ba9c 100644 --- a/services/locations/src/components/ShareData/ShareData.styled.js +++ b/services/locations/src/components/ShareData/ShareData.styled.js @@ -20,15 +20,12 @@ export const Content = styled.div` `; export const RequestWrapper = styled.div` position: relative; - color: rgba(0, 0, 0, 0.87); display: flex; flex-direction: column; justify-content: space-between; margin: 40px auto 0; width: 656px; - padding: 40px 48px 80px; - background: #f3f5f7; border-radius: 2px; `; @@ -52,18 +49,18 @@ export const SubHeader = styled.h5` export const InfoBlock = styled.p` font-size: 14px; font-weight: 500; - margin-bottom: 21px; + margin-bottom: 24px; width: 524px; `; export const RequestContent = styled.div` - margin-bottom: 40px; + margin-bottom: 24px; `; export const FinishButtonWrapper = styled.div` display: flex; justify-content: ${({ align }) => align}; - margin-top: 40px; + margin-top: 24px; `; export const UploadMessage = styled.p` diff --git a/services/locations/src/components/ShareData/ShareDataStep/Checkins/Checkins.react.js b/services/locations/src/components/ShareData/ShareDataStep/Checkins/Checkins.react.js new file mode 100644 index 0000000..f81a9be --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/Checkins/Checkins.react.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; + +import { RequestContent } from '../../ShareData.styled'; +import { StyledLabel, StyledValue } from '../ShareDataStep.styled'; + +export const Checkins = ({ transfers }) => { + const intl = useIntl(); + + return ( + <RequestContent> + <StyledLabel> + {intl.formatMessage({ id: 'shareData.activeCheckIns' })} + </StyledLabel> + <StyledValue> + {transfers.reduce((sum, transfer) => sum + transfer.traces.length, 0)} + </StyledValue> + </RequestContent> + ); +}; diff --git a/services/locations/src/components/ShareData/ShareDataStep/Checkins/index.js b/services/locations/src/components/ShareData/ShareDataStep/Checkins/index.js new file mode 100644 index 0000000..d61ad17 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/Checkins/index.js @@ -0,0 +1 @@ +export { Checkins } from './Checkins.react'; diff --git a/services/locations/src/components/ShareData/ShareDataStep/DataRequests/DataRequests.react.js b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/DataRequests.react.js new file mode 100644 index 0000000..2827386 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/DataRequests.react.js @@ -0,0 +1,54 @@ +import React from 'react'; + +import moment from 'moment'; +import { useIntl } from 'react-intl'; + +import { RequestContent } from '../../ShareData.styled'; +import { + StyledLabel, + StyledValue, + StyledTransfer, +} from '../ShareDataStep.styled'; + +import { RequestWarning } from './RequestWarning'; + +const REQUEST_TIME_WARINING = 72; + +export const DataRequests = ({ transfers }) => { + const intl = useIntl(); + + const timestampFormat = 'DD.MM.YYYY hh:mm'; + + const formatTimeStamp = timestamp => + `${moment.unix(timestamp).format(timestampFormat)} ${intl.formatMessage({ + id: 'dataTransfers.transfer.timeLabel', + })}`; + + const isRequestTimeSuspicious = (startTime, endTime) => { + const duration = moment.duration( + moment.unix(endTime).diff(moment.unix(startTime)) + ); + const requestTime = duration.asHours(); + return requestTime > REQUEST_TIME_WARINING; + }; + + return ( + <RequestContent> + <StyledLabel> + {intl.formatMessage({ id: 'shareData.dataRequest' })} + </StyledLabel> + {transfers.map(transfer => ( + <StyledTransfer key={transfer.transferId}> + <StyledValue> + {transfer.location.name} + {isRequestTimeSuspicious(transfer.time[0], transfer.time[1]) && ( + <RequestWarning /> + )} + </StyledValue> + <StyledValue>{`${formatTimeStamp(transfer.time[0])} -`}</StyledValue> + <StyledValue>{formatTimeStamp(transfer.time[1])}</StyledValue> + </StyledTransfer> + ))} + </RequestContent> + ); +}; diff --git a/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.react.js b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.react.js new file mode 100644 index 0000000..1abc674 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.react.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import { Tooltip } from 'antd'; +import Icon from '@ant-design/icons'; + +// Assets +import { ReactComponent as RequestWarningSVG } from 'assets/warning.svg'; + +import { IconWrapper } from './RequestWarning.styled'; + +const RequestWarningIcon = () => ( + <Icon component={RequestWarningSVG} style={{ fontSize: 16 }} /> +); + +export const RequestWarning = () => { + const intl = useIntl(); + + return ( + <Tooltip title={intl.formatMessage({ id: 'requestWarning.info' })}> + <IconWrapper> + <RequestWarningIcon /> + </IconWrapper> + </Tooltip> + ); +}; diff --git a/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.styled.js b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.styled.js new file mode 100644 index 0000000..5edf6e1 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/RequestWarning.styled.js @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const IconWrapper = styled.div` + display: flex; + margin-left: 8px; +`; diff --git a/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/index.js b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/index.js new file mode 100644 index 0000000..63fcfe8 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/RequestWarning/index.js @@ -0,0 +1 @@ +export { RequestWarning } from './RequestWarning.react'; diff --git a/services/locations/src/components/ShareData/ShareDataStep/DataRequests/index.js b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/index.js new file mode 100644 index 0000000..2e188d6 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/DataRequests/index.js @@ -0,0 +1 @@ +export { DataRequests } from './DataRequests.react'; diff --git a/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/HealthDepartmentInfo.react.js b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/HealthDepartmentInfo.react.js new file mode 100644 index 0000000..1178fa5 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/HealthDepartmentInfo.react.js @@ -0,0 +1,41 @@ +import React, { useMemo } from 'react'; +import { useIntl } from 'react-intl'; +import { RequestContent } from '../../ShareData.styled'; +import { + StyledLabel, + StyledValue, + StyledHealthDepartment, +} from '../ShareDataStep.styled'; +import { VerificationTag } from './VerificationTag'; + +export const HealthDepartmentInfo = ({ transfers }) => { + const intl = useIntl(); + + const healthDepartments = useMemo(() => { + const departments = {}; + + if (!transfers) return []; + + for (const transfer of transfers) { + departments[transfer.department.name] = transfer.department; + } + + return Object.values(departments); + }, [transfers]); + + return ( + <RequestContent> + <StyledLabel> + {intl.formatMessage({ id: 'shareData.inquiringHD' })} + </StyledLabel> + {healthDepartments.map(healthDepartment => ( + <StyledValue key={healthDepartment.name}> + <StyledHealthDepartment> + {healthDepartment.name} + <VerificationTag healthDepartment={healthDepartment} /> + </StyledHealthDepartment> + </StyledValue> + ))} + </RequestContent> + ); +}; diff --git a/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.react.js b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.react.js new file mode 100644 index 0000000..e8a6873 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.react.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import { Tooltip } from 'antd'; +import Icon from '@ant-design/icons'; + +// Assets +import { ReactComponent as VerificationSVG } from 'assets/verification.svg'; + +import { IconWrapper } from './VerificationTag.styled'; + +const VerificationIcon = () => ( + <Icon component={VerificationSVG} style={{ fontSize: 16 }} /> +); + +export const VerificationTag = ({ healthDepartment }) => { + const intl = useIntl(); + + if (!healthDepartment.signedPublicHDEKP) return null; + + return ( + <Tooltip title={intl.formatMessage({ id: 'verificationTag.info' })}> + <IconWrapper> + <VerificationIcon /> + </IconWrapper> + </Tooltip> + ); +}; diff --git a/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.styled.js b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.styled.js new file mode 100644 index 0000000..5edf6e1 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/VerificationTag.styled.js @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const IconWrapper = styled.div` + display: flex; + margin-left: 8px; +`; diff --git a/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/index.js b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/index.js new file mode 100644 index 0000000..5bff6cb --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/VerificationTag/index.js @@ -0,0 +1 @@ +export { VerificationTag } from './VerificationTag.react'; diff --git a/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/index.js b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/index.js new file mode 100644 index 0000000..73359de --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/HealthDepartmentInfo/index.js @@ -0,0 +1 @@ +export { HealthDepartmentInfo } from './HealthDepartmentInfo.react'; diff --git a/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.react.js b/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.react.js index 761291b..1a27dae 100644 --- a/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.react.js +++ b/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.react.js @@ -1,6 +1,5 @@ -import React, { useMemo } from 'react'; +import React from 'react'; -import moment from 'moment'; import { notification } from 'antd'; import { PrimaryButton } from 'components/general'; import { useIntl } from 'react-intl'; @@ -15,6 +14,12 @@ import { FinishButtonWrapper, } from '../ShareData.styled'; +import { Checkins } from './Checkins'; +import { HealthDepartmentInfo } from './HealthDepartmentInfo'; +import { DataRequests } from './DataRequests'; + +import { InfoText } from './ShareDataStep.styled'; + /** * This step decrypts the outer encryption layer of the traces requested by * the health department for the corresponding locationTransfer process. Any @@ -54,7 +59,6 @@ export const ShareDataStep = ({ additionalData: reencryptedAdditionalData, }; } catch (error) { - // eslint-disable-next-line no-console console.error('Trace decryption failed.', trace, error); return null; } @@ -78,20 +82,6 @@ export const ShareDataStep = ({ ); }; - const timestampFormat = 'DD.MM.YYYY hh:mm'; - - const healthDepartments = useMemo(() => { - const departments = {}; - - if (!transfers) return []; - - for (const transfer of transfers) { - departments[transfer.department.name] = transfer.department; - } - - return Object.values(departments); - }, [transfers]); - return ( <> {showStepLabel && <StepLabel>2/2</StepLabel>} @@ -100,42 +90,18 @@ export const ShareDataStep = ({ <SubHeader> {intl.formatMessage({ id: 'shareData.shareData' })} </SubHeader> - - <h4>{intl.formatMessage({ id: 'shareData.transfersLabel' })}</h4> - - {transfers.map(transfer => ( - <h3 key={transfer.transferId}> - {transfer.location.name} - <br /> - {` ${moment - .unix(transfer.time[0]) - .format(timestampFormat)} ${intl.formatMessage({ - id: 'dataTransfers.transfer.timeLabel', - })} - ${moment - .unix(transfer.time[1]) - - .format(timestampFormat)} ${intl.formatMessage({ - id: 'dataTransfers.transfer.timeLabel', - })}`} - </h3> - ))} - </RequestContent> - - <RequestContent> - <h4>{intl.formatMessage({ id: 'shareData.activeCheckIns' })}</h4> - - <h3> - {transfers.reduce((sum, transfer) => sum + transfer.traces.length, 0)} - </h3> - </RequestContent> - - <RequestContent> - <h4>{intl.formatMessage({ id: 'shareData.inquiringHD' })}</h4> - - {healthDepartments.map(healthDepartment => ( - <h3 key={healthDepartment.name}>{healthDepartment.name}</h3> - ))} + <InfoText> + {intl.formatMessage( + { id: 'shareData.transfersLabel' }, + { + note: <b>{intl.formatMessage({ id: 'shareData.note' })}</b>, + } + )} + </InfoText> </RequestContent> + <DataRequests transfers={transfers} /> + <Checkins transfers={transfers} /> + <HealthDepartmentInfo transfers={transfers} /> <FinishButtonWrapper align="flex-end"> <PrimaryButton data-cy="next" onClick={onFinish}> {intl.formatMessage({ id: 'shareData.finish' })} diff --git a/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.styled.js b/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.styled.js new file mode 100644 index 0000000..bb02a89 --- /dev/null +++ b/services/locations/src/components/ShareData/ShareDataStep/ShareDataStep.styled.js @@ -0,0 +1,30 @@ +import styled from 'styled-components'; + +export const InfoText = styled.div` + font-size: 16px; + margin-bottom: 24px; +`; + +export const StyledLabel = styled.div` + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; +`; + +export const StyledValue = styled.div` + font-size: 16px; + font-weight: bold; + display: flex; + align-items: center; +`; + +export const StyledTransfer = styled.div` + display: flex; + flex-direction: column; + margin-bottom: 24px; +`; + +export const StyledHealthDepartment = styled.div` + display: flex; + align-items: center; +`; diff --git a/services/locations/src/messages/de.json b/services/locations/src/messages/de.json index 3b98702..ccd1660 100644 --- a/services/locations/src/messages/de.json +++ b/services/locations/src/messages/de.json @@ -161,7 +161,7 @@ "shareData.finish": "Fertig", "shareData.privkey.error.description": "Dein privater Schlüssel ist nicht korrekt", "shareData.shareData": "Datenfreigabe bestätigen", - "shareData.transfersLabel": "Check-ins während des gewünschten Zeitraums", + "shareData.transfersLabel": "{note}: Gesundheitsämter fragen in der Regel nicht mehr als 72 Stunden an Check-In-Daten an. Bitte vergewissere dich, dass die vorliegende Anfrage legitim ist.", "shareData.activeCheckIns": "Summe der Check-ins", "shareData.passwordStep.info": "Zum Entschlüsseln der Daten wird dein persönliches Passwort benötigt. Das Passwort hast du bei der Erstellung des Treffens erhalten. Bitte gib das Passwort hier ein, um den Datenzugriff durch das Gesundheitsamt zu ermöglichen.", "shareData.finish.text": "Deine Daten wurden für das Gesundheitsamt freigegeben.", @@ -639,5 +639,9 @@ "modal.guestList.checkoutDate": "Checkout Datum", "modal.guestList.checkoutTime": "Checkout Zeit", "shareData.privateKey.keySize": "Die hochgeladene Datei war zu groß.", - "profile.services.support": "Hilfe" + "profile.services.support": "Hilfe", + "shareData.note": "HINWEIS", + "shareData.dataRequest": "Location und Anfragezeitraum", + "verificationTag.info": "Dieses Gesundheitsamt ist verifiziert", + "requestWarning.info": "Achtung, der angefragte Zeitraum ist ungewöhnlich lang. In der Regel fragen Gesundheitsämter nicht mehr als 72 Stunden an Check-in Daten an. Bitte prüfe die Anfrage." } \ No newline at end of file diff --git a/services/locations/src/messages/en.json b/services/locations/src/messages/en.json index f55c90b..9060a89 100644 --- a/services/locations/src/messages/en.json +++ b/services/locations/src/messages/en.json @@ -161,7 +161,7 @@ "shareData.finish": "Done", "shareData.privkey.error.description": "Your private key is not correct", "shareData.shareData": "Confirm data release", - "shareData.transfersLabel": "Check-ins during the requested period", + "shareData.transfersLabel": "{note}: Usually health departments do not request data for more than 72 hours. Please make sure the current share data request is valid.", "shareData.activeCheckIns": "Number of check ins", "shareData.passwordStep.info": "To decrypt your data the health department needs the password you received when setting up your meeting. Please enter your password to grant access.", "shareData.finish.text": "Your data has been shared with the health department.", @@ -639,5 +639,9 @@ "modal.guestList.checkoutDate": "Check out date", "modal.guestList.checkoutTime": "Check out time", "shareData.privateKey.keySize": "The uploaded file was too big.", - "profile.services.support": "Support" + "profile.services.support": "Support", + "shareData.note": "NOTE", + "shareData.dataRequest": "Location and request time", + "verificationTag.info": "This health department is verified", + "requestWarning.info": "Caution, the requested time is longer than usual. Normally health departments do not request data from check ins for more than 72 hours. Please recheck this request." } \ No newline at end of file diff --git a/services/locations/src/utils/checkCharacter.js b/services/locations/src/utils/checkCharacter.js index 8919792..def01b3 100644 --- a/services/locations/src/utils/checkCharacter.js +++ b/services/locations/src/utils/checkCharacter.js @@ -1,4 +1,4 @@ -const SAFE_CHARACTERS_REGEX = /^[\w !&+./:@`|£À-ÿÄăąćĉċÄđēėęěÄğģĥħĩīįİıĵķĸĺļłńņÅőœŗřśÅşšţŦũūÅůűųŵŷźżžơưếệ–-]*$/i; +const SAFE_CHARACTERS_REGEX = /^[\w !&()+,./:@`|£À-ÿÄăąćĉċÄđēėęěÄğģĥħĩīįİıĵķĸĺļłńņÅőœŗřśÅşšţŦũūÅůűųŵŷźżžơưếệ–-]*$/i; const NO_HTTP_REGEX = /^((?!http).)*$/i; const NO_FTP_REGEX = /^((?!ftp).)*$/i; diff --git a/services/locations/src/utils/downloadPDF.js b/services/locations/src/utils/downloadPDF.js index 1758ab7..2f16438 100644 --- a/services/locations/src/utils/downloadPDF.js +++ b/services/locations/src/utils/downloadPDF.js @@ -16,11 +16,9 @@ export const downloadPDF = async ({ isCWAEventEnabled, }) => { const messageText = intl.formatMessage({ id: 'message.generatingPDF' }); - const locationName = - location.name || - intl.formatMessage({ - id: 'location.defaultName', - }); + const locationName = `${location.groupName}${ + location.name ? ` - ${location.name}` : '' + }`; const showLoadingProgress = percentage => openLoadingMessage(percentage, messageText); setIsDownloading(true); @@ -34,10 +32,6 @@ export const downloadPDF = async ({ intl.formatMessage({ id: 'modal.qrCodeDocument.table', }), - intl.formatMessage({ - id: 'modal.qrCodeDocument.message', - }), - locationName, TABLE_KEY, proxy(showLoadingProgress) ); diff --git a/services/locations/src/workers/createQRCodePDF.worker.js b/services/locations/src/workers/createQRCodePDF.worker.js index 1b31ed2..9ee720f 100644 --- a/services/locations/src/workers/createQRCodePDF.worker.js +++ b/services/locations/src/workers/createQRCodePDF.worker.js @@ -42,14 +42,43 @@ const generateQRData = ( return qrData; }; -const drawQRCode = (pdf, x, y, base64QR, title, subtitle) => { - pdf.text(title, 35 + x, 10 + y, 'center'); +const drawCuttedText = (text, length, pdf, x, y) => { + const parts = pdf.splitTextToSize(text, length); + const isTextFitting = parts.length === 1; + pdf.text(`${parts[0]}${isTextFitting ? '' : '...'}`, x, y, { + align: 'center', + }); +}; + +const drawQRCode = ({ + pdf, + x, + y, + base64QR, + areaName = '', + table = '', + locationName, +}) => { + const maxTextLength = 40; + if (areaName) { + pdf.setFontSize(9); + pdf.setFont(undefined, 'bold'); + const yOffset = table ? 10 : 15; + drawCuttedText(areaName, maxTextLength, pdf, 35 + x, yOffset + y); + } + if (table) { + pdf.setFontSize(8); + pdf.setFont(undefined, 'normal'); + drawCuttedText(table, maxTextLength, pdf, 35 + x, 15 + y); + } pdf.rect(0 + x, 0 + y, 70, 74.25); - pdf.addImage(base64QR, 'png', 15 + x, 15 + y, 40, 40); + pdf.addImage(base64QR, 'png', 15 + x, 17 + y, 40, 40); pdf.setFillColor('#FFFFFF'); - pdf.rect(29 + x, 31 + y, 12, 8, 'F'); - pdf.addImage(LUCA_SVG_BASE_64, 'png', 31 + x, 33 + y, 8, 4); - pdf.text(subtitle, 35 + x, 62 + y, 'center'); + pdf.rect(29 + x, 33 + y, 12, 8, 'F'); + pdf.addImage(LUCA_SVG_BASE_64, 'png', 31 + x, 35 + y, 8, 4); + pdf.setFontSize(9); + pdf.setFont(undefined, 'bold'); + drawCuttedText(locationName, maxTextLength, pdf, 35 + x, 62 + y); }; const PDFCreator = { @@ -58,14 +87,11 @@ const PDFCreator = { isTableQRCodeEnabled, isCWAEventEnabled, path, - title, - subtitle, - name, + table, keyName, progressCallback ) { const pdf = new jsPDF(); - pdf.setFontSize(15); const qrCodeData = generateQRData( location, @@ -86,14 +112,15 @@ const PDFCreator = { size: 800, }); id += 1; - drawQRCode( + drawQRCode({ pdf, - row * 70, - column * 74.25, + x: row * 70, + y: column * 74.25, base64QR, - `${title} ${id}`, - subtitle - ); + areaName: location.name, + table: `${table} ${id}`, + locationName: location.groupName, + }); const percentage = Math.round((id / qrCodeData.length) * 100); progressCallback(percentage); } @@ -105,7 +132,14 @@ const PDFCreator = { const base64QR = qrcode(qrCodeData[0], { size: 800, }); - drawQRCode(pdf, 0, 0, base64QR, `${name}`, subtitle); + drawQRCode({ + pdf, + x: 0, + y: 0, + base64QR, + areaName: location.name, + locationName: location.groupName, + }); } return pdf.output('blob'); }, diff --git a/services/scanner/.iyarc b/services/scanner/.iyarc index 4c9ba97..01b4ae7 100644 --- a/services/scanner/.iyarc +++ b/services/scanner/.iyarc @@ -3,3 +3,15 @@ # Regular Expression Denial of Service (ReDoS) # This is only used by react-scripts for development purposes. 1755 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32804 +# Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization +# This is only used by react-scripts for development purposes. +1770 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32803 +# Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning +# This is only used by react-scripts for development purposes. +1771 \ No newline at end of file diff --git a/services/scanner/package.json b/services/scanner/package.json index 0edce4e..d34c59c 100644 --- a/services/scanner/package.json +++ b/services/scanner/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/scanner", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/scanner/src/components/CamScanner/CamScanner.react.js b/services/scanner/src/components/CamScanner/CamScanner.react.js index fa40c05..27ecf04 100644 --- a/services/scanner/src/components/CamScanner/CamScanner.react.js +++ b/services/scanner/src/components/CamScanner/CamScanner.react.js @@ -146,6 +146,7 @@ export const CamScanner = ({ scanner }) => { delay={SCAN_TIMEOUT} onError={error => notifyScanError(error, intl)} onScan={handleScan} + resolution={1200} /> )} {isSuccess && ( diff --git a/services/scanner/src/constants/timeouts.js b/services/scanner/src/constants/timeouts.js index 45f62ce..f7cf528 100644 --- a/services/scanner/src/constants/timeouts.js +++ b/services/scanner/src/constants/timeouts.js @@ -1,4 +1,4 @@ -export const SCAN_TIMEOUT = 3000; +export const SCAN_TIMEOUT = 700; // 5 min in ms export const REFETCH_INTERVAL_MS = 300000; diff --git a/services/webapp/.iyarc b/services/webapp/.iyarc index 4c9ba97..01b4ae7 100644 --- a/services/webapp/.iyarc +++ b/services/webapp/.iyarc @@ -3,3 +3,15 @@ # Regular Expression Denial of Service (ReDoS) # This is only used by react-scripts for development purposes. 1755 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32804 +# Arbitrary File Creation/Overwrite due to insufficient absolute path sanitization +# This is only used by react-scripts for development purposes. +1770 + +# tar +# https://nvd.nist.gov/vuln/detail/CVE-2021-32803 +# Arbitrary File Creation/Overwrite via insufficient symlink protection due to directory cache poisoning +# This is only used by react-scripts for development purposes. +1771 \ No newline at end of file diff --git a/services/webapp/package.json b/services/webapp/package.json index 92ea0b6..f67f1a0 100644 --- a/services/webapp/package.json +++ b/services/webapp/package.json @@ -1,6 +1,6 @@ { "name": "@lucaapp/webapp", - "version": "1.6.2", + "version": "1.7.0", "private": true, "license": "Apache-2.0", "author": "Culture4Life <hello@luca-app.de> (https://www.luca-app.de/)", diff --git a/services/webapp/src/App.react.js b/services/webapp/src/App.react.js index 298f77d..dc36062 100644 --- a/services/webapp/src/App.react.js +++ b/services/webapp/src/App.react.js @@ -39,14 +39,13 @@ import { CheckOut } from 'components/Checkout'; import { OnBoarding } from 'components/OnBoarding'; import { ContactInformation } from 'components/ContactInformation'; import { FeatureNotImplemented } from 'components/FeatureNotImplemented'; - import { AuthenticationWrapper } from 'components/AuthenticationWrapper.react'; import { PrivateMeeting } from 'components/PrivateMeeting/PrivateMeeting.react'; - +import { UserSessionProvider } from 'contexts/userSessionContext'; +import { configureStore } from 'configureStore'; import { AppWrapper } from './App.styled'; import { messages } from './messages'; -import { configureStore } from './configureStore'; const history = createBrowserHistory(); const store = configureStore(undefined, history); @@ -73,38 +72,43 @@ export const Main = () => { <ConnectedRouter history={history}> <ErrorWrapper> <AuthenticationWrapper> - <Switch> - <Route path={LICENSES_ROUTE} component={Licenses} /> - <Route - path={`${ON_BOARDING_PATH}/:scannerId/`} - component={OnBoarding} - /> - <Route path={ON_BOARDING_PATH} component={OnBoarding} /> - <Route path={`${CHECK_OUT_PATH}/`} component={CheckOut} /> - <Route - path={EDIT_CONTACT_INFORMATION_SETTING} - component={ContactInformation} - /> - <Route path={HISTORY_PATH} component={History} /> - <Route path={SETTINGS_PATH} component={Settings} /> - <Route - path={[APPOINTMENT_PATH, COVID_TEST_PATH]} - component={FeatureNotImplemented} - /> - <Route - component={Home} - path={CHECKIN_TO_PRIVATE_MEETING_PATH} - /> - <Route - path={BASE_PRIVATE_MEETING_PATH} - component={PrivateMeeting} - /> - <Route - component={Home} - path={`${HOME_PATH}/:scannerId`} - /> - <Route path={HOME_PATH} component={Home} /> - </Switch> + <UserSessionProvider> + <Switch> + <Route path={LICENSES_ROUTE} component={Licenses} /> + <Route + path={`${ON_BOARDING_PATH}/:scannerId/`} + component={OnBoarding} + /> + <Route path={ON_BOARDING_PATH} component={OnBoarding} /> + <Route + path={`${CHECK_OUT_PATH}/`} + component={CheckOut} + /> + <Route + path={EDIT_CONTACT_INFORMATION_SETTING} + component={ContactInformation} + /> + <Route path={HISTORY_PATH} component={History} /> + <Route path={SETTINGS_PATH} component={Settings} /> + <Route + path={[APPOINTMENT_PATH, COVID_TEST_PATH]} + component={FeatureNotImplemented} + /> + <Route + component={Home} + path={CHECKIN_TO_PRIVATE_MEETING_PATH} + /> + <Route + path={BASE_PRIVATE_MEETING_PATH} + component={PrivateMeeting} + /> + <Route + component={Home} + path={`${HOME_PATH}/:scannerId`} + /> + <Route path={HOME_PATH} component={Home} /> + </Switch> + </UserSessionProvider> </AuthenticationWrapper> </ErrorWrapper> </ConnectedRouter> diff --git a/services/webapp/src/components/AuthenticationWrapper.react.js b/services/webapp/src/components/AuthenticationWrapper.react.js index f597e16..3d15539 100644 --- a/services/webapp/src/components/AuthenticationWrapper.react.js +++ b/services/webapp/src/components/AuthenticationWrapper.react.js @@ -1,26 +1,24 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; - import { notification } from 'antd'; import { useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import useInterval from '@use-it/interval'; import { useHistory } from 'react-router-dom'; import { getLocation } from 'connected-react-router'; - import { indexDB } from 'db'; import { - HOME_PATH, - CHECK_OUT_PATH, - ON_BOARDING_PATH, - COVID_TEST_PATH, APPOINTMENT_PATH, BASE_PRIVATE_MEETING_PATH, + CHECK_OUT_PATH, + COVID_TEST_PATH, + HOME_PATH, + ON_BOARDING_PATH, } from 'constants/routes'; import { - syncHistory, checkHistory, - checkSession, checkLocalHistory, + checkSession, + syncHistory, } from 'helpers/history'; import { getCheckOutPath } from 'helpers/routes'; import { checkForActiveHostedPrivateMeeting } from 'helpers/privateMeeting'; diff --git a/services/webapp/src/components/Checkout/Checkout.react.js b/services/webapp/src/components/Checkout/Checkout.react.js index 5dd9143..0163e82 100644 --- a/services/webapp/src/components/Checkout/Checkout.react.js +++ b/services/webapp/src/components/Checkout/Checkout.react.js @@ -1,37 +1,37 @@ -import React, { useEffect, useState, useMemo, useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import moment from 'moment'; import { notification } from 'antd'; import { useIntl } from 'react-intl'; import { Helmet } from 'react-helmet'; -import useInterval from '@use-it/interval'; import { useHistory } from 'react-router-dom'; import { base64UrlToBytes } from '@lucaapp/crypto'; import { checkout } from 'helpers/crypto'; import { getSession } from 'helpers/history'; import { getLocation } from 'helpers/locations'; -import { AppContent, AppLayout } from 'components/AppLayout'; -import { HOME_PATH, CHECK_OUT_LOCATION_TYPE } from 'constants/routes'; +import { AppLayout } from 'components/AppLayout'; +import { CHECK_OUT_LOCATION_TYPE, HOME_PATH } from 'constants/routes'; import { WEBAPP_WARNING_MODAL_SHOWN_SESSION_KEY } from 'constants/storage'; +import { useTraceClock } from 'hooks/useTraceClock'; +import { useUserSession } from 'contexts/userSessionContext'; import { - StyledTime, - StyledTimeType, - StyledInfoText, StyledAppHeadline, - StyledCheckInTime, - StyledCheckoutButton, + StyledInfoText, StyledLocationInfoText, - StyledCheckInTimeContainer, - StyledLocationInfoContainer, - StyledLocationInfoTextContainer, - StyledNumberOfAccountsOnThisLocation, + StyledWrapper, } from './Checkout.styled'; +import { CheckoutButton } from './CheckoutButton'; +import { TimerDisplay } from './TimerDisplay'; +import { LocationInfoContainer } from './LocationInfoContainer'; export function CheckOut({ location: { search } }) { const intl = useIntl(); const history = useHistory(); + const { checkin, setCheckin } = useUserSession(); + const clock = useTraceClock(); + const { traceId, type } = useMemo(() => { const searchParameters = new URLSearchParams(search); return { @@ -39,37 +39,9 @@ export function CheckOut({ location: { search } }) { type: searchParameters.get('type') || CHECK_OUT_LOCATION_TYPE, }; }, [search]); - const [clock, setClock] = useState({ - hour: '00', - minute: '00', - seconds: '00', - }); const [session, setSession] = useState(); const [location, setLocation] = useState(); const [additionalData, setAdditionalData] = useState(); - const [isCheckoutAllowed, setIsCheckoutAllowed] = useState(false); - - const traceClock = useCallback(() => { - if (session?.checkin) { - const time = moment.duration(moment().diff(moment.unix(session.checkin))); - setClock({ - hour: time.hours().toString().padStart(2, '0'), - minute: time.minutes().toString().padStart(2, '0'), - seconds: time.seconds().toString().padStart(2, '0'), - }); - - if (time.minutes() >= 2) { - setIsCheckoutAllowed(true); - } else { - setIsCheckoutAllowed(false); - } - } - }, [session?.checkin]); - - useInterval(traceClock, 1000); - useEffect(() => { - traceClock(); - }, [traceClock]); const handleError = useCallback(() => { notification.error({ @@ -83,7 +55,7 @@ export function CheckOut({ location: { search } }) { }, [intl]); useEffect(() => { - if (session && session.locationId) { + if (session && session?.locationId) { getLocation(session.locationId) .then(apiLocation => { setLocation(apiLocation); @@ -100,24 +72,24 @@ export function CheckOut({ location: { search } }) { getSession(traceId) .then(apiSession => { if (!apiSession) { + setCheckin(null); history.push(HOME_PATH); return; } - if (!apiSession.checkout) { - const time = moment.duration( - moment().diff(moment.unix(apiSession.checkin)) - ); - if (time.minutes() < 1 && time.hours() === 0) { - setSession({ ...apiSession, checkin: moment().unix() }); - return; - } + if (apiSession.checkin && checkin) { + setCheckin(checkin); + } else { + setCheckin(moment().unix()); + } - setSession(apiSession); + if (apiSession.checkout) { + const timestamp = moment().unix(); + setCheckin(null); + setSession({ ...apiSession, checkin: timestamp }); return; } - - history.push(HOME_PATH); + setSession(apiSession); }) .catch(() => { handleError(); @@ -125,20 +97,25 @@ export function CheckOut({ location: { search } }) { } else { history.push(HOME_PATH); } - }, [history, traceId, intl, type, handleError]); + }, [history, traceId, intl, type, handleError, setCheckin, checkin]); useEffect(() => { if (!window.location.hash) return; - const [key, value] = Object.entries( JSON.parse(base64UrlToBytes(window.location.hash.slice(1))) )[0]; - const label = key === 'table' ? intl.formatMessage({ id: 'Checkout.table' }) : key; setAdditionalData(`${label}: ${value}`); - }, [intl]); + }, [intl, setCheckin]); + + const onCheckout = async () => { + await checkout(traceId); + setCheckin(null); + sessionStorage.removeItem(WEBAPP_WARNING_MODAL_SHOWN_SESSION_KEY); + history.push(HOME_PATH); + }; return ( <> @@ -153,64 +130,29 @@ export function CheckOut({ location: { search } }) { } bgColor="linear-gradient(-180deg, rgb(211, 222, 195) 0%, rgb(132, 137, 101) 100%);" > - <AppContent noCentering> - <StyledLocationInfoContainer> - <StyledLocationInfoTextContainer> - <StyledLocationInfoText> - {intl.formatMessage({ id: 'Checkout.YouAreCheckIn' })} + <LocationInfoContainer session={session}> + {additionalData && + Object.keys(additionalData).map(valueKey => ( + <StyledLocationInfoText key={valueKey}> + {valueKey[0].toUpperCase() + valueKey.slice(1)}:{' '} + {additionalData[valueKey]} </StyledLocationInfoText> - <StyledLocationInfoText> - {intl.formatMessage({ id: 'Checkout.CheckIn' })}{' '} - {session?.checkin && - `${moment - .unix(session?.checkin) - .format('DD.MM.YYYY - HH.mm')} Uhr`} - </StyledLocationInfoText> - {additionalData && - Object.keys(additionalData).map(valueKey => ( - <StyledLocationInfoText key={valueKey}> - {valueKey[0].toUpperCase() + valueKey.slice(1)}:{' '} - {additionalData[valueKey]} - </StyledLocationInfoText> - ))} - </StyledLocationInfoTextContainer> - <StyledNumberOfAccountsOnThisLocation /> - </StyledLocationInfoContainer> - </AppContent> - <AppContent> + ))} + </LocationInfoContainer> + + <StyledWrapper> <StyledInfoText> {intl.formatMessage({ id: 'Checkout.CurrentLocation' })} </StyledInfoText> - <StyledCheckInTimeContainer> - <StyledCheckInTime> - <StyledTime>{clock.hour}</StyledTime> - <StyledTimeType>h</StyledTimeType> - </StyledCheckInTime> - <StyledCheckInTime> - <StyledTime data-cy="clockMinutes">:{clock.minute}</StyledTime> - <StyledTimeType isNotHours>min</StyledTimeType> - </StyledCheckInTime> - <StyledCheckInTime> - <StyledTime>:{clock.seconds}</StyledTime> - <StyledTimeType isNotHours>s</StyledTimeType> - </StyledCheckInTime> - </StyledCheckInTimeContainer> - </AppContent> - <AppContent> - <StyledCheckoutButton - tabIndex="1" - id="checkout" - data-cy="checkout" - disabled={!isCheckoutAllowed} - onClick={async () => { - await checkout(traceId); - history.push(HOME_PATH); - sessionStorage.removeItem(WEBAPP_WARNING_MODAL_SHOWN_SESSION_KEY); - }} - > - {intl.formatMessage({ id: 'Checkout.CheckOutButton' })} - </StyledCheckoutButton> - </AppContent> + <TimerDisplay + hour={clock.hour} + minute={clock.minute} + seconds={clock.seconds} + /> + </StyledWrapper> + <CheckoutButton disabled={!clock.allowCheckout} onClick={onCheckout}> + {intl.formatMessage({ id: 'Checkout.CheckOutButton' })} + </CheckoutButton> </AppLayout> </> ); diff --git a/services/webapp/src/components/Checkout/Checkout.styled.js b/services/webapp/src/components/Checkout/Checkout.styled.js index 9b09a2b..1d5ae9c 100644 --- a/services/webapp/src/components/Checkout/Checkout.styled.js +++ b/services/webapp/src/components/Checkout/Checkout.styled.js @@ -1,32 +1,17 @@ -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; -import { AppHeadline } from '../AppLayout'; -import { SecondaryButton } from '../Buttons'; -import { SmallHeadline, Text } from '../Text'; +import { AppHeadline } from 'components/AppLayout'; +import { SecondaryButton } from 'components/Buttons'; +import { Text } from 'components/Text'; export const StyledAppHeadline = styled(AppHeadline)` overflow: hidden; - padding-top: 16px; + padding: 4px 0 0; text-overflow: ellipsis; `; -export const StyledLocationInfoContainer = styled.div` - display: flex; -`; -export const StyledLocationInfoTextContainer = styled.div` - flex: 1; - display: flex; - flex-direction: column; -`; -export const AdditionalDataText = styled(Text)` - margin-top: 20px; -`; + export const StyledLocationInfoText = styled(Text)``; -export const StyledNumberOfAccountsOnThisLocation = styled(SmallHeadline)` - color: #000; - display: flex; - align-items: flex-start; - justify-content: flex-start; -`; + export const AddNewPersonButton = styled(SecondaryButton)` width: 100%; height: 36px; @@ -48,27 +33,18 @@ export const StyledInfoText = styled(Text)` color: #000; padding-top: 8px; `; -export const StyledCheckInTimeContainer = styled.div` + +export const StyledWrapper = styled.div` + flex: ${({ flex = 1 }) => flex}; display: flex; - margin: 16px 0; - align-items: center; - justify-content: center; -`; -export const StyledCheckInTime = styled.div``; -export const StyledTime = styled(SmallHeadline)` - color: #000; - font-size: 64px; -`; -export const StyledTimeType = styled(Text)` - color: #000; - padding: 0 0 0 ${({ isNotHours }) => (isNotHours ? 13 : 0)}px; -`; -export const StyledCheckoutButton = styled(SecondaryButton)` - width: 100%; - color: #000; - height: 56px; - border: none; - font-size: 18px; - padding: 0 40px; - background-color: #bed4c2; + padding: 0 ${({ noPadding }) => (noPadding ? 0 : 16)}px; + overflow-y: scroll; + overflow-x: hidden; + flex-direction: column; + ${({ isCentered }) => + !isCentered && + css` + align-items: center; + justify-content: center; + `} `; diff --git a/services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.react.js b/services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.react.js new file mode 100644 index 0000000..234527b --- /dev/null +++ b/services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.react.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { CheckoutButtonWrapper, StyledButton } from './CheckoutButton.styled'; + +export const CheckoutButton = ({ disabled, children, onClick }) => { + return ( + <CheckoutButtonWrapper> + <StyledButton + tabIndex="1" + id="checkout" + data-cy="checkout" + disabled={disabled} + onClick={onClick} + > + {children} + </StyledButton> + </CheckoutButtonWrapper> + ); +}; diff --git a/services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.styled.js b/services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.styled.js new file mode 100644 index 0000000..ba53dca --- /dev/null +++ b/services/webapp/src/components/Checkout/CheckoutButton/CheckoutButton.styled.js @@ -0,0 +1,23 @@ +import styled from 'styled-components'; +import { SecondaryButton } from 'components/Buttons'; + +export const CheckoutButtonWrapper = styled.div` + flex: 1; + display: flex; + padding: 0 16px; + overflow-y: scroll; + overflow-x: hidden; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +export const StyledButton = styled(SecondaryButton)` + width: 100%; + color: #000; + height: 56px; + border: none; + font-size: 18px; + padding: 0 40px; + background-color: #bed4c2; +`; diff --git a/services/webapp/src/components/Checkout/CheckoutButton/index.js b/services/webapp/src/components/Checkout/CheckoutButton/index.js new file mode 100644 index 0000000..e2b7b93 --- /dev/null +++ b/services/webapp/src/components/Checkout/CheckoutButton/index.js @@ -0,0 +1 @@ +export * from './CheckoutButton.react'; diff --git a/services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.react.js b/services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.react.js new file mode 100644 index 0000000..0a15e85 --- /dev/null +++ b/services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.react.js @@ -0,0 +1,30 @@ +import React from 'react'; +import moment from 'moment'; +import { useIntl } from 'react-intl'; +import { + StyledLocationInfoContainer, + StyledLocationInfoText, + StyledLocationInfoTextContainer, + StyledNumberOfAccountsOnThisLocation, +} from './LocationInfoContainer.styled'; + +export const LocationInfoContainer = ({ children, session }) => { + const intl = useIntl(); + + return ( + <StyledLocationInfoContainer> + <StyledLocationInfoTextContainer> + <StyledLocationInfoText> + {intl.formatMessage({ id: 'Checkout.YouAreCheckIn' })} + </StyledLocationInfoText> + <StyledLocationInfoText> + {intl.formatMessage({ id: 'Checkout.CheckIn' })} + {session?.checkin && + `${moment.unix(session?.checkin).format('DD.MM.YYYY HH:mm')} Uhr`} + </StyledLocationInfoText> + {children} + </StyledLocationInfoTextContainer> + <StyledNumberOfAccountsOnThisLocation /> + </StyledLocationInfoContainer> + ); +}; diff --git a/services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.styled.js b/services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.styled.js new file mode 100644 index 0000000..16dd8da --- /dev/null +++ b/services/webapp/src/components/Checkout/LocationInfoContainer/LocationInfoContainer.styled.js @@ -0,0 +1,22 @@ +import styled from 'styled-components'; +import { SmallHeadline, Text } from 'components/Text'; + +export const StyledLocationInfoContainer = styled.div` + display: flex; + padding-left: 20px; +`; + +export const StyledLocationInfoText = styled(Text)``; + +export const StyledLocationInfoTextContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; +`; + +export const StyledNumberOfAccountsOnThisLocation = styled(SmallHeadline)` + color: #000; + display: flex; + align-items: flex-start; + justify-content: flex-start; +`; diff --git a/services/webapp/src/components/Checkout/LocationInfoContainer/index.js b/services/webapp/src/components/Checkout/LocationInfoContainer/index.js new file mode 100644 index 0000000..50b9f3d --- /dev/null +++ b/services/webapp/src/components/Checkout/LocationInfoContainer/index.js @@ -0,0 +1 @@ +export * from './LocationInfoContainer.react'; diff --git a/services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.react.js b/services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.react.js new file mode 100644 index 0000000..5ab6f27 --- /dev/null +++ b/services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.react.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { + StyledTime, + StyledTimeType, + TimeContainer, +} from './TimerDisplay.styled'; + +export const TimerDisplay = ({ hour, minute, seconds }) => { + return ( + <TimeContainer> + <div> + <StyledTime>{hour}</StyledTime> + <StyledTimeType>h</StyledTimeType> + </div> + <div> + <StyledTime data-cy="clockMinutes">:{minute}</StyledTime> + <StyledTimeType isHours>min</StyledTimeType> + </div> + <div> + <StyledTime>:{seconds}</StyledTime> + <StyledTimeType isHours>s</StyledTimeType> + </div> + </TimeContainer> + ); +}; diff --git a/services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.styled.js b/services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.styled.js new file mode 100644 index 0000000..dd53c51 --- /dev/null +++ b/services/webapp/src/components/Checkout/TimerDisplay/TimerDisplay.styled.js @@ -0,0 +1,19 @@ +import styled from 'styled-components'; +import { SmallHeadline, Text } from 'components/Text'; + +export const TimeContainer = styled.div` + display: flex; + margin: 16px 0; + align-items: center; + justify-content: center; +`; + +export const StyledTime = styled(SmallHeadline)` + color: #000; + font-size: 64px; +`; + +export const StyledTimeType = styled(Text)` + color: #000; + padding: 0 0 0 ${({ isHours }) => (isHours ? 13 : 0)}px; +`; diff --git a/services/webapp/src/components/Checkout/TimerDisplay/index.js b/services/webapp/src/components/Checkout/TimerDisplay/index.js new file mode 100644 index 0000000..35ff932 --- /dev/null +++ b/services/webapp/src/components/Checkout/TimerDisplay/index.js @@ -0,0 +1 @@ +export * from './TimerDisplay.react'; diff --git a/services/webapp/src/components/Home/Home.react.js b/services/webapp/src/components/Home/Home.react.js index 0138fa8..821fdb5 100644 --- a/services/webapp/src/components/Home/Home.react.js +++ b/services/webapp/src/components/Home/Home.react.js @@ -7,9 +7,9 @@ import { useHistory } from 'react-router-dom'; import { indexDB } from 'db'; import { + BASE_PRIVATE_MEETING_PATH, HISTORY_PATH, SETTINGS_PATH, - BASE_PRIVATE_MEETING_PATH, } from 'constants/routes'; import { createMeeting } from 'helpers/privateMeeting'; @@ -20,16 +20,16 @@ import { CheckinIcon, HistoryIcon } from 'components/Icons'; import { AppContent, AppHeadline, AppLayout } from 'components/AppLayout'; import { + StyledFooterContainer, + StyledFooterItem, + StyledHeaderMenuIconContainer, StyledLucaLogo, StyledMenuIcon, + StyledPrivateMeetingButton, StyledQRCodeInfo, - StyledFooterItem, + StyledQRCodeInfoContainer, StyledQRCodeWrapper, StyledSecondaryButton, - StyledQRCodeInfoContainer, - StyledPrivateMeetingButton, - StyledHeaderMenuIconContainer, - StyledFooterContainer, } from './Home.styled'; import { SelfCheckin } from './SelfCheckin'; import { WebAppWarningModal } from './WebAppWarningModal'; diff --git a/services/webapp/src/components/Home/SelfCheckin/SelfCheckin.react.js b/services/webapp/src/components/Home/SelfCheckin/SelfCheckin.react.js index 41fbb92..6f06de9 100644 --- a/services/webapp/src/components/Home/SelfCheckin/SelfCheckin.react.js +++ b/services/webapp/src/components/Home/SelfCheckin/SelfCheckin.react.js @@ -1,21 +1,18 @@ import React, { useCallback, useState } from 'react'; - import { notification } from 'antd'; import { useIntl } from 'react-intl'; + import { useHistory } from 'react-router-dom'; -import { decodeUtf8, base64UrlToBytes } from '@lucaapp/crypto'; +import { base64UrlToBytes, decodeUtf8 } from '@lucaapp/crypto'; import { BASE_PRIVATE_MEETING_PATH } from 'constants/routes'; import { CWA_URL_SPLIT } from 'constants/cwa'; - import { checkin } from 'helpers/crypto'; import { getCheckOutPath } from 'helpers/routes'; import { AccountDeletedError } from 'network/api'; import { checkinToPrivateMeeting } from 'helpers/privateMeeting'; - import { AppContent } from 'components/AppLayout'; import { QRCodeScanner } from 'components/QRCodeScanner/QRCodeScanner.react'; - import { PrivateMeetingWarningModal } from '../PrivateMeetingWarningModal'; export function SelfCheckin({ onClose }) { @@ -83,7 +80,7 @@ export function SelfCheckin({ onClose }) { }); }); }, - [history, privateMeetingQRCodeData, formatMessage] + [privateMeetingQRCodeData, history, formatMessage] ); return ( diff --git a/services/webapp/src/components/Home/useCheckInCheck.js b/services/webapp/src/components/Home/useCheckInCheck.js index 7bde0e4..2f2bd30 100644 --- a/services/webapp/src/components/Home/useCheckInCheck.js +++ b/services/webapp/src/components/Home/useCheckInCheck.js @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { notification } from 'antd'; import { useIntl } from 'react-intl'; -import { decodeUtf8, base64UrlToBytes } from '@lucaapp/crypto'; +import { base64UrlToBytes, decodeUtf8 } from '@lucaapp/crypto'; import { useHistory } from 'react-router-dom'; import { getScanner } from 'network/api'; diff --git a/services/webapp/src/components/Home/useTraceQRCode.js b/services/webapp/src/components/Home/useTraceQRCode.js index 0a6fa53..cca832c 100644 --- a/services/webapp/src/components/Home/useTraceQRCode.js +++ b/services/webapp/src/components/Home/useTraceQRCode.js @@ -5,15 +5,20 @@ import useInterval from '@use-it/interval'; import { generateQRCode } from 'helpers/crypto'; import { isLocalTimeCorrect } from 'helpers/time'; +import { useUserSession } from 'contexts/userSessionContext'; export function useTraceQRCode(users) { const intl = useIntl(); + const { setCheckin } = useUserSession(); const [qrCode, setQRCode] = useState(''); useEffect(() => { isLocalTimeCorrect() .then(isTimeCorrect => { - if (isTimeCorrect) return; + if (isTimeCorrect) { + setCheckin(null); + return; + } notification.error({ message: intl.formatMessage({ id: 'error.systemTime', @@ -21,7 +26,7 @@ export function useTraceQRCode(users) { }); }) .catch(console.error); - }, [intl]); + }, [intl, setCheckin]); const generateTraceQRCode = useMemo(() => { return async () => { diff --git a/services/webapp/src/contexts/userSessionContext.js b/services/webapp/src/contexts/userSessionContext.js new file mode 100644 index 0000000..8b53b45 --- /dev/null +++ b/services/webapp/src/contexts/userSessionContext.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { useLocalStorage } from 'hooks/useLocalStorage'; +import { LUCA_CHECKIN_KEY } from 'helper'; + +/** Context * */ +const defaultValues = { checkin: null }; +const UserSessionContext = React.createContext(defaultValues); + +/** Provider * */ +export const UserSessionProvider = ({ + children, + defaultValue = defaultValues, +}) => { + const [checkInTimeStamp, setCheckInTimeStamp] = useLocalStorage( + LUCA_CHECKIN_KEY, + defaultValue.checkin + ); + + return ( + <UserSessionContext.Provider + value={{ + checkin: checkInTimeStamp, + setCheckin: setCheckInTimeStamp, + }} + > + {children} + </UserSessionContext.Provider> + ); +}; + +/** Hook * */ +export const useUserSession = () => { + return React.useContext(UserSessionContext); +}; diff --git a/services/webapp/src/helper.js b/services/webapp/src/helper.js index 8bd429a..87c2969 100644 --- a/services/webapp/src/helper.js +++ b/services/webapp/src/helper.js @@ -7,6 +7,7 @@ export const LUCA_USER_DATA_KEY = 'LUCA_USER_DATA'; export const LUCA_USER_CHECKIN_TRACEID_KEY = 'LUCA_USER_CHECKIN_TRACEID'; export const LUCA_TIMESTAMP_LAST_CHECKIN_KEY = 'LUCA_TIMESTAMP_LAST_CHECKIN'; +export const LUCA_CHECKIN_KEY = 'LUCA_CHECKIN'; export function getUserIdFromLocalStorage() { return localStorage.getItem(LUCA_USER_ID_KEY); diff --git a/services/webapp/src/hooks/useLocalStorage.js b/services/webapp/src/hooks/useLocalStorage.js new file mode 100644 index 0000000..477dd43 --- /dev/null +++ b/services/webapp/src/hooks/useLocalStorage.js @@ -0,0 +1,12 @@ +import { useState } from 'react'; + +export const useLocalStorage = (key, defaultValue) => { + const [value, setValue] = useState(() => { + const storedValue = window.localStorage.getItem(key); + return storedValue !== null ? JSON.parse(storedValue) : defaultValue; + }); + + window.localStorage.setItem(key, JSON.stringify(value)); + + return [value, setValue]; +}; diff --git a/services/webapp/src/hooks/useTraceClock.js b/services/webapp/src/hooks/useTraceClock.js new file mode 100644 index 0000000..f965d3d --- /dev/null +++ b/services/webapp/src/hooks/useTraceClock.js @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react'; +import moment from 'moment'; +import { useUserSession } from 'contexts/userSessionContext'; + +export const useTraceClock = () => { + const { checkin } = useUserSession(); + const [clock, setClock] = useState( + moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }) + ); + + useEffect(() => { + const timeout = setTimeout(() => { + setClock(moment.duration(moment().diff(moment.unix(checkin)))); + }, 1000); + return () => { + clearTimeout(timeout); + }; + }); + + return { + allowCheckout: clock.minutes() >= 2, + hour: clock ? clock.hours().toString().padStart(2, '0') : '00', + minute: clock ? clock.minutes().toString().padStart(2, '0') : '00', + seconds: clock ? clock.seconds().toString().padStart(2, '0') : '00', + }; +}; diff --git a/services/webapp/yarn.lock b/services/webapp/yarn.lock index f10f236..c5ae130 100644 --- a/services/webapp/yarn.lock +++ b/services/webapp/yarn.lock @@ -69,10 +69,10 @@ dependencies: "@babel/highlight" "^7.14.5" -"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.5.tgz#8ef4c18e58e801c5c95d3c1c0f2874a2680fadea" - integrity sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w== +"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.5", "@babel/compat-data@^7.14.7": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.7.tgz#7b047d7a3a89a67d2258dc61f604f098f1bc7e08" + integrity sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw== "@babel/core@7.12.3": version "7.12.3" @@ -97,16 +97,16 @@ source-map "^0.5.0" "@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.12.16", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.5.tgz#d281f46a9905f07d1b3bf71ead54d9c7d89cb1e3" - integrity sha512-RN/AwP2DJmQTZSfiDaD+JQQ/J99KsIpOCfBE5pL+5jJSt7nI3nYGoAXZu+ffYSQ029NLs2DstZb+eR81uuARgg== + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.6.tgz#e0814ec1a950032ff16c13a2721de39a8416fcab" + integrity sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA== dependencies: "@babel/code-frame" "^7.14.5" "@babel/generator" "^7.14.5" "@babel/helper-compilation-targets" "^7.14.5" "@babel/helper-module-transforms" "^7.14.5" - "@babel/helpers" "^7.14.5" - "@babel/parser" "^7.14.5" + "@babel/helpers" "^7.14.6" + "@babel/parser" "^7.14.6" "@babel/template" "^7.14.5" "@babel/traverse" "^7.14.5" "@babel/types" "^7.14.5" @@ -118,9 +118,9 @@ source-map "^0.5.0" "@babel/eslint-parser@^7.12.16": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.14.5.tgz#441c04e2fe9825ea628c2b4e5524d40129cbbccd" - integrity sha512-20BlOHuGf3UXS7z1QPyllM9Gz8SEgcp/UcKeUmdHIFZO6HF1n+3KaLpeyfwWvjY/Os/ynPX3k8qXE/nZ5dw/0g== + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.14.7.tgz#91be59a4f7dd60d02a3ef772d156976465596bda" + integrity sha512-6WPwZqO5priAGIwV6msJcdc9TsEPzYeYdS/Xuoap+/ihkgN6dzHp2bcAAwyWZ5bLzk0vvjDmKvRwkqNaiJ8BiQ== dependencies: eslint-scope "^5.1.1" eslint-visitor-keys "^2.1.0" @@ -160,10 +160,10 @@ browserslist "^4.16.6" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.5.tgz#8842ec495516dd1ed8f6c572be92ba78b1e9beef" - integrity sha512-Uq9z2e7ZtcnDMirRqAGLRaLwJn+Lrh388v5ETrR3pALJnElVh2zqQmdbz4W2RUJYohAPh2mtyPUgyMHMzXMncQ== +"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.14.6": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz#f114469b6c06f8b5c59c6c4e74621f5085362542" + integrity sha512-Z6gsfGofTxH/+LQXqYEK45kxmcensbzmk/oi8DmaQytlQCgqNZt9XQF8iqlI/SeXWVjaMNxvYvzaYw+kh42mDg== dependencies: "@babel/helper-annotate-as-pure" "^7.14.5" "@babel/helper-function-name" "^7.14.5" @@ -225,9 +225,9 @@ "@babel/types" "^7.14.5" "@babel/helper-member-expression-to-functions@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.5.tgz#d5c70e4ad13b402c95156c7a53568f504e2fb7b8" - integrity sha512-UxUeEYPrqH1Q/k0yRku1JE7dyfyehNwT6SVkMHvYvPDv4+uu627VXBckVj891BO8ruKBkiDoGnZf4qPDD8abDQ== + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz#97e56244beb94211fe277bd818e3a329c66f7970" + integrity sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA== dependencies: "@babel/types" "^7.14.5" @@ -324,10 +324,10 @@ "@babel/traverse" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helpers@^7.12.1", "@babel/helpers@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.5.tgz#4870f8d9a6fdbbd65e5674a3558b4ff7fef0d9b2" - integrity sha512-xtcWOuN9VL6nApgVHtq3PPcQv5qFBJzoSZzJ/2c0QK/IP/gxVcoWSNQwFEGvmbQsuS9rhYqjILDGGXcTkA705Q== +"@babel/helpers@^7.12.1", "@babel/helpers@^7.14.6": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.6.tgz#5b58306b95f1b47e2a0199434fa8658fa6c21635" + integrity sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA== dependencies: "@babel/template" "^7.14.5" "@babel/traverse" "^7.14.5" @@ -342,10 +342,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.3", "@babel/parser@^7.14.5", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.5.tgz#4cd2f346261061b2518873ffecdf1612cb032829" - integrity sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.3", "@babel/parser@^7.14.5", "@babel/parser@^7.14.6", "@babel/parser@^7.14.7", "@babel/parser@^7.7.0", "@babel/parser@^7.8.3": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.7.tgz#6099720c8839ca865a2637e6c85852ead0bdb595" + integrity sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA== "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": version "7.14.5" @@ -356,10 +356,10 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" "@babel/plugin-proposal-optional-chaining" "^7.14.5" -"@babel/plugin-proposal-async-generator-functions@^7.12.1", "@babel/plugin-proposal-async-generator-functions@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.5.tgz#4024990e3dd74181f4f426ea657769ff49a2df39" - integrity sha512-tbD/CG3l43FIXxmu4a7RBe4zH7MLJ+S/lFowPFO7HetS2hyOZ/0nnnznegDuzFzfkyQYTxqdTH/hKmuBngaDAA== +"@babel/plugin-proposal-async-generator-functions@^7.12.1", "@babel/plugin-proposal-async-generator-functions@^7.14.7": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.7.tgz#784a48c3d8ed073f65adcf30b57bcbf6c8119ace" + integrity sha512-RK8Wj7lXLY3bqei69/cc25gwS5puEc3dknoFPFbqfy3XxYQBQFvu4ioWpafMBAB+L9NyptQK4nMOa5Xz16og8Q== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-remap-async-to-generator" "^7.14.5" @@ -463,12 +463,12 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.5.tgz#e581d5ccdfa187ea6ed73f56c6a21c1580b90fbf" - integrity sha512-VzMyY6PWNPPT3pxc5hi9LloKNr4SSrVCg7Yr6aZpW4Ym07r7KqSU/QXYwjXLVxqwSv0t/XSXkFoKBPUkZ8vb2A== +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.14.7": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz#5920a2b3df7f7901df0205974c0641b13fd9d363" + integrity sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g== dependencies: - "@babel/compat-data" "^7.14.5" + "@babel/compat-data" "^7.14.7" "@babel/helper-compilation-targets" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" @@ -716,10 +716,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.5.tgz#d32ad19ff1a6da1e861dc62720d80d9776e3bf35" - integrity sha512-wU9tYisEbRMxqDezKUqC9GleLycCRoUsai9ddlsq54r8QRLaeEhc+d+9DqCG+kV9W2GgQjTZESPTpn5bAFMDww== +"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.14.7": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz#0ad58ed37e23e22084d109f185260835e5557576" + integrity sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" @@ -821,10 +821,10 @@ "@babel/helper-module-transforms" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1", "@babel/plugin-transform-named-capturing-groups-regex@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.5.tgz#d537e8ee083ee6f6aa4f4eef9d2081d555746e4c" - integrity sha512-+Xe5+6MWFo311U8SchgeX5c1+lJM+eZDBZgD+tvXu9VVQPXwwVzeManMMjYX6xw2HczngfOSZjoFYKwdeB/Jvw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.12.1", "@babel/plugin-transform-named-capturing-groups-regex@^7.14.7": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.7.tgz#60c06892acf9df231e256c24464bfecb0908fd4e" + integrity sha512-DTNOTaS7TkW97xsDMrp7nycUVh6sn/eq22VaxWfEdzuEbRsiaOU0pqU7DlyUGHVsbQbSghvjKRpEl+nUCKGQSg== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.14.5" @@ -949,10 +949,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-spread@^7.12.1", "@babel/plugin-transform-spread@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.5.tgz#bd269fb4119754d2ce7f4cc39a96b4f71baae356" - integrity sha512-/3iqoQdiWergnShZYl0xACb4ADeYCJ7X/RgmwtXshn6cIvautRPAFzhd58frQlokLO6Jb4/3JXvmm6WNTPtiTw== +"@babel/plugin-transform-spread@^7.12.1", "@babel/plugin-transform-spread@^7.14.6": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz#6bd40e57fe7de94aa904851963b5616652f73144" + integrity sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" @@ -979,11 +979,11 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-transform-typescript@^7.12.1": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.14.5.tgz#5b41b59072f765bd1ec1d0b694e08c7df0f6f8a0" - integrity sha512-cFD5PKp4b8/KkwQ7h71FdPXFvz1RgwTFF9akRZwFldb9G0AHf7CgoPx96c4Q/ZVjh6V81tqQwW5YiHws16OzPg== + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.14.6.tgz#6e9c2d98da2507ebe0a883b100cde3c7279df36c" + integrity sha512-XlTdBq7Awr4FYIzqhmYY80WN0V0azF74DMPyFqVHBvf81ZUgc4X7ZOpx6O8eLDK6iM5cCQzeyJw0ynTaefixRA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-create-class-features-plugin" "^7.14.6" "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript" "^7.14.5" @@ -1075,16 +1075,16 @@ semver "^5.5.0" "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.8.4": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.5.tgz#c0c84e763661fd0e74292c3d511cb33b0c668997" - integrity sha512-ci6TsS0bjrdPpWGnQ+m4f+JSSzDKlckqKIJJt9UZ/+g7Zz9k0N8lYU8IeLg/01o2h8LyNZDMLGgRLDTxpudLsA== + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.7.tgz#5c70b22d4c2d893b03d8c886a5c17422502b932a" + integrity sha512-itOGqCKLsSUl0Y+1nSfhbuuOlTs0MJk2Iv7iSH+XT/mR8U1zRLO7NjWlYXB47yhK4J/7j+HYty/EhFZDYKa/VA== dependencies: - "@babel/compat-data" "^7.14.5" + "@babel/compat-data" "^7.14.7" "@babel/helper-compilation-targets" "^7.14.5" "@babel/helper-plugin-utils" "^7.14.5" "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.14.5" - "@babel/plugin-proposal-async-generator-functions" "^7.14.5" + "@babel/plugin-proposal-async-generator-functions" "^7.14.7" "@babel/plugin-proposal-class-properties" "^7.14.5" "@babel/plugin-proposal-class-static-block" "^7.14.5" "@babel/plugin-proposal-dynamic-import" "^7.14.5" @@ -1093,7 +1093,7 @@ "@babel/plugin-proposal-logical-assignment-operators" "^7.14.5" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5" "@babel/plugin-proposal-numeric-separator" "^7.14.5" - "@babel/plugin-proposal-object-rest-spread" "^7.14.5" + "@babel/plugin-proposal-object-rest-spread" "^7.14.7" "@babel/plugin-proposal-optional-catch-binding" "^7.14.5" "@babel/plugin-proposal-optional-chaining" "^7.14.5" "@babel/plugin-proposal-private-methods" "^7.14.5" @@ -1119,7 +1119,7 @@ "@babel/plugin-transform-block-scoping" "^7.14.5" "@babel/plugin-transform-classes" "^7.14.5" "@babel/plugin-transform-computed-properties" "^7.14.5" - "@babel/plugin-transform-destructuring" "^7.14.5" + "@babel/plugin-transform-destructuring" "^7.14.7" "@babel/plugin-transform-dotall-regex" "^7.14.5" "@babel/plugin-transform-duplicate-keys" "^7.14.5" "@babel/plugin-transform-exponentiation-operator" "^7.14.5" @@ -1131,7 +1131,7 @@ "@babel/plugin-transform-modules-commonjs" "^7.14.5" "@babel/plugin-transform-modules-systemjs" "^7.14.5" "@babel/plugin-transform-modules-umd" "^7.14.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.7" "@babel/plugin-transform-new-target" "^7.14.5" "@babel/plugin-transform-object-super" "^7.14.5" "@babel/plugin-transform-parameters" "^7.14.5" @@ -1139,7 +1139,7 @@ "@babel/plugin-transform-regenerator" "^7.14.5" "@babel/plugin-transform-reserved-words" "^7.14.5" "@babel/plugin-transform-shorthand-properties" "^7.14.5" - "@babel/plugin-transform-spread" "^7.14.5" + "@babel/plugin-transform-spread" "^7.14.6" "@babel/plugin-transform-sticky-regex" "^7.14.5" "@babel/plugin-transform-template-literals" "^7.14.5" "@babel/plugin-transform-typeof-symbol" "^7.14.5" @@ -1150,7 +1150,7 @@ babel-plugin-polyfill-corejs2 "^0.2.2" babel-plugin-polyfill-corejs3 "^0.2.2" babel-plugin-polyfill-regenerator "^0.2.2" - core-js-compat "^3.14.0" + core-js-compat "^3.15.0" semver "^6.3.0" "@babel/preset-modules@^0.1.3", "@babel/preset-modules@^0.1.4": @@ -1198,11 +1198,11 @@ "@babel/plugin-transform-typescript" "^7.12.1" "@babel/runtime-corejs3@^7.10.2": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.14.5.tgz#0d9bf00d59c0b73185c462c323efffd0f4c37283" - integrity sha512-cBbwXj3F2xjnQJ0ERaFRLjxhUSBYsQPXJ7CERz/ecx6q6hzQ99eTflAPFC3ks4q/IG4CWupNVdflc4jlFBJVsg== + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.14.7.tgz#0ef292bbce40ca00f874c9724ef175a12476465c" + integrity sha512-Wvzcw4mBYbTagyBVZpAJWI06auSIj033T/yNE0Zn1xcup83MieCddZA7ls3kme17L4NOGBrQ09Q+nKB41RLWBA== dependencies: - core-js-pure "^3.14.0" + core-js-pure "^3.15.0" regenerator-runtime "^0.13.4" "@babel/runtime@7.12.1": @@ -1213,9 +1213,9 @@ regenerator-runtime "^0.13.4" "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.5.tgz#665450911c6031af38f81db530f387ec04cd9a98" - integrity sha512-121rumjddw9c3NCQ55KGkyE1h/nzWhU/owjhw0l4mQrkzz4x9SGS1X8gFLraHwX7td3Yo4QTL+qj0NcIzN87BA== + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" + integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== dependencies: regenerator-runtime "^0.13.4" @@ -1229,16 +1229,16 @@ "@babel/types" "^7.14.5" "@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.5.tgz#c111b0f58afab4fea3d3385a406f692748c59870" - integrity sha512-G3BiS15vevepdmFqmUc9X+64y0viZYygubAMO8SvBmKARuF6CPSZtH4Ng9vi/lrWlZFGe3FWdXNy835akH8Glg== + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.7.tgz#64007c9774cfdc3abd23b0780bc18a3ce3631753" + integrity sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ== dependencies: "@babel/code-frame" "^7.14.5" "@babel/generator" "^7.14.5" "@babel/helper-function-name" "^7.14.5" "@babel/helper-hoist-variables" "^7.14.5" "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/parser" "^7.14.5" + "@babel/parser" "^7.14.7" "@babel/types" "^7.14.5" debug "^4.1.0" globals "^11.1.0" @@ -1834,9 +1834,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.11.1" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.11.1.tgz#654f6c4f67568e24c23b367e947098c6206fa639" - integrity sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw== + version "7.14.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.0.tgz#a34277cf8acbd3185ea74129e1f100491eb1da7f" + integrity sha512-IilJZ1hJBUZwMOVDNTdflOOLzJB/ZtljYVa7k3gEZN/jqIJIPkWHC6dvbX+DD2CwZDHB9wAKzZPzzqMIkW37/w== dependencies: "@babel/types" "^7.3.0" @@ -1933,9 +1933,9 @@ integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== "@types/node@*": - version "15.12.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d" - integrity sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww== + version "15.12.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.5.tgz#9a78318a45d75c9523d2396131bd3cca54b2d185" + integrity sha512-se3yX7UHv5Bscf8f1ERKvQOD6sTyycH3hdaoozvaLxgUiY5lIGEeH37AD0G0Qi9kPqihPn0HOfd2yaIEN9VwEg== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -2053,28 +2053,27 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^4.5.0": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz#b9c7313321cb837e2bf8bebe7acc2220659e67d3" - integrity sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw== + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz#1a66f03b264844387beb7dc85e1f1d403bd1803f" + integrity sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ== dependencies: - "@typescript-eslint/experimental-utils" "4.26.1" - "@typescript-eslint/scope-manager" "4.26.1" + "@typescript-eslint/experimental-utils" "4.28.0" + "@typescript-eslint/scope-manager" "4.28.0" debug "^4.3.1" functional-red-black-tree "^1.0.1" - lodash "^4.17.21" regexpp "^3.1.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.26.1", "@typescript-eslint/experimental-utils@^4.0.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz#a35980a2390da9232aa206b27f620eab66e94142" - integrity sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ== +"@typescript-eslint/experimental-utils@4.28.0", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz#13167ed991320684bdc23588135ae62115b30ee0" + integrity sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ== dependencies: "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.26.1" - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/typescript-estree" "4.26.1" + "@typescript-eslint/scope-manager" "4.28.0" + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/typescript-estree" "4.28.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" @@ -2090,32 +2089,32 @@ eslint-utils "^2.0.0" "@typescript-eslint/parser@^4.5.0": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.26.1.tgz#cecfdd5eb7a5c13aabce1c1cfd7fbafb5a0f1e8e" - integrity sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ== + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.28.0.tgz#2404c16751a28616ef3abab77c8e51d680a12caa" + integrity sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A== dependencies: - "@typescript-eslint/scope-manager" "4.26.1" - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/typescript-estree" "4.26.1" + "@typescript-eslint/scope-manager" "4.28.0" + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/typescript-estree" "4.28.0" debug "^4.3.1" -"@typescript-eslint/scope-manager@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz#075a74a15ff33ee3a7ed33e5fce16ee86689f662" - integrity sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ== +"@typescript-eslint/scope-manager@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz#6a3009d2ab64a30fc8a1e257a1a320067f36a0ce" + integrity sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg== dependencies: - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/visitor-keys" "4.26.1" + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/visitor-keys" "4.28.0" "@typescript-eslint/types@3.10.1": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== -"@typescript-eslint/types@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.26.1.tgz#9e7c523f73c34b04a765e4167ca5650436ef1d38" - integrity sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg== +"@typescript-eslint/types@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.28.0.tgz#a33504e1ce7ac51fc39035f5fe6f15079d4dafb0" + integrity sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA== "@typescript-eslint/typescript-estree@3.10.1": version "3.10.1" @@ -2131,13 +2130,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz#b2ce2e789233d62283fae2c16baabd4f1dbc9633" - integrity sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg== +"@typescript-eslint/typescript-estree@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz#e66d4e5aa2ede66fec8af434898fe61af10c71cf" + integrity sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ== dependencies: - "@typescript-eslint/types" "4.26.1" - "@typescript-eslint/visitor-keys" "4.26.1" + "@typescript-eslint/types" "4.28.0" + "@typescript-eslint/visitor-keys" "4.28.0" debug "^4.3.1" globby "^11.0.3" is-glob "^4.0.1" @@ -2151,12 +2150,12 @@ dependencies: eslint-visitor-keys "^1.1.0" -"@typescript-eslint/visitor-keys@4.26.1": - version "4.26.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz#0d55ea735cb0d8903b198017d6d4f518fdaac546" - integrity sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw== +"@typescript-eslint/visitor-keys@4.28.0": + version "4.28.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz#255c67c966ec294104169a6939d96f91c8a89434" + integrity sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw== dependencies: - "@typescript-eslint/types" "4.26.1" + "@typescript-eslint/types" "4.28.0" eslint-visitor-keys "^2.0.0" "@use-it/interval@1.0.0": @@ -2361,9 +2360,9 @@ acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4: - version "8.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.0.tgz#af53266e698d7cffa416714b503066a82221be60" - integrity sha512-ULr0LDaEqQrMFGyQ3bhJkLsbtrQ8QibAseGZeaSUiT/6zb9IvIkomWHJIvgvwad+hinRAgsI51JcWk2yvwyL+w== + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== address@1.1.2, address@^1.0.1: version "1.1.2" @@ -2535,7 +2534,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.1: +anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -2749,9 +2748,9 @@ autoprefixer@^9.6.1, autoprefixer@^9.8.6: postcss-value-parser "^4.1.0" axe-core@^4.0.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.2.tgz#0c987d82c8b82b4b9b7a945f1b5ef0d8fed586ed" - integrity sha512-OKRkKM4ojMEZRJ5UNJHmq9tht7cEnRnqKG6KyB/trYws00Xtkv12mHtlJ0SK7cmuNbrU8dPUova3ELTuilfBbw== + version "4.2.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.3.tgz#2a3afc332f0031b42f602f4a3de03c211ca98f72" + integrity sha512-pXnVMfJKSIWU2Ml4JHP7pZEPIrgBO1Fd3WGx+fPBsS+KRGhE4vxooD8XBGWbQOIVSZsVK7pUDBBkCicNu80yzQ== axobject-query@^2.2.0: version "2.2.0" @@ -2854,12 +2853,12 @@ babel-plugin-polyfill-corejs2@^0.2.2: semver "^6.1.1" babel-plugin-polyfill-corejs3@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.2.tgz#7424a1682ee44baec817327710b1b094e5f8f7f5" - integrity sha512-l1Cf8PKk12eEk5QP/NQ6TH8A1pee6wWDJ96WjxrMXFLHLOBFzYM4moG80HFgduVhTqAFez4alnZKEhP/bYHg0A== + version "0.2.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz#72add68cf08a8bf139ba6e6dfc0b1d504098e57b" + integrity sha512-rCOFzEIJpJEAU14XCcV/erIf/wZQMmMT5l5vXOpL5uoznyOGfDIjPj6FVytMvtzaKSTSVKouOCTPJ5OMUZH30g== dependencies: "@babel/helper-define-polyfill-provider" "^0.2.2" - core-js-compat "^3.9.1" + core-js-compat "^3.14.0" babel-plugin-polyfill-regenerator@^0.2.2: version "0.2.2" @@ -3408,9 +3407,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: - version "1.0.30001237" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz#4b7783661515b8e7151fc6376cfd97f0e427b9e5" - integrity sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw== + version "1.0.30001240" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001240.tgz#ec15d125b590602c8731545c5351ff054ad2d52f" + integrity sha512-nb8mDzfMdxBDN7ZKx8chWafAdBp5DAAlpWvNyUGe5tcDWd838zpzDN3Rah9cjCqhfOKkrvx40G2SDtP0qiWX/w== capture-exit@^2.0.0: version "2.0.0" @@ -3486,19 +3485,19 @@ chokidar@^2.1.8: fsevents "^1.2.7" chokidar@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" chownr@^1.1.1: version "1.1.4" @@ -3796,7 +3795,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -3808,6 +3807,13 @@ convert-source-map@^0.3.3: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -3849,18 +3855,18 @@ copy-to-clipboard@^3.2.0: dependencies: toggle-selection "^1.0.6" -core-js-compat@^3.14.0, core-js-compat@^3.6.2, core-js-compat@^3.9.1: - version "3.14.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.14.0.tgz#b574dabf29184681d5b16357bd33d104df3d29a5" - integrity sha512-R4NS2eupxtiJU+VwgkF9WTpnSfZW4pogwKHd8bclWU2sp93Pr5S1uYJI84cMOubJRou7bcfL0vmwtLslWN5p3A== +core-js-compat@^3.14.0, core-js-compat@^3.15.0, core-js-compat@^3.6.2: + version "3.15.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.15.1.tgz#1afe233716d37ee021956ef097594071b2b585a7" + integrity sha512-xGhzYMX6y7oEGQGAJmP2TmtBLvR4nZmRGEcFa3ubHOq5YEp51gGN9AovVa0AoujGZIq+Wm6dISiYyGNfdflYww== dependencies: browserslist "^4.16.6" semver "7.0.0" -core-js-pure@^3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.14.0.tgz#72bcfacba74a65ffce04bf94ae91d966e80ee553" - integrity sha512-YVh+LN2FgNU0odThzm61BsdkwrbrchumFq3oztnE9vTKC4KS2fvnPmcx8t6jnqAyOTCTF4ZSiuK8Qhh7SNcL4g== +core-js-pure@^3.15.0: + version "3.15.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.15.1.tgz#8356f6576fa2aa8e54ca6fe02620968ff010eed7" + integrity sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA== core-js@^2.4.0: version "2.6.12" @@ -3868,9 +3874,9 @@ core-js@^2.4.0: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.6.5: - version "3.14.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.14.0.tgz#62322b98c71cc2018b027971a69419e2425c2a6c" - integrity sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA== + version "3.15.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.1.tgz#6c08ab88abdf56545045ccf5fd81f47f407e7f1a" + integrity sha512-h8VbZYnc9pDzueiS2610IULDkpFFPunHwIpl8yRwFahAEEdSpHlTy3h3z3rKq5h11CaUdBEeRViu9AYvbxiMeg== core-util-is@~1.0.0: version "1.0.2" @@ -4293,9 +4299,9 @@ decamelize@^1.1.0, decamelize@^1.2.0: integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decimal.js@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" - integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== decode-uri-component@^0.2.0: version "0.2.0" @@ -4627,9 +4633,9 @@ ejs@^2.6.1: integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== electron-to-chromium@^1.3.723: - version "1.3.752" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz#0728587f1b9b970ec9ffad932496429aef750d09" - integrity sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A== + version "1.3.759" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.759.tgz#b0d652d376831470a4c230ba721da2427bfb996a" + integrity sha512-nM76xH0t2FBH5iMEZDVc3S/qbdKjGH7TThezxC8k1Q7w7WHvIAyJh8lAe2UamGfdRqBTjHfPDn82LJ0ksCiB9g== elliptic@6.5.4, elliptic@^6.5.3: version "6.5.4" @@ -5092,9 +5098,9 @@ eslint-webpack-plugin@^2.5.2: schema-utils "^3.0.0" eslint@^7.11.0: - version "7.28.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.28.0.tgz#435aa17a0b82c13bb2be9d51408b617e49c1e820" - integrity sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g== + version "7.29.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.29.0.tgz#ee2a7648f2e729485e4d0bd6383ec1deabc8b3c0" + integrity sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA== dependencies: "@babel/code-frame" "7.12.11" "@eslint/eslintrc" "^0.4.2" @@ -5377,16 +5383,15 @@ fast-diff@^1.1.2: integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== fast-glob@^3.1.1, fast-glob@^3.2.5: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== + version "3.2.6" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.6.tgz#434dd9529845176ea049acc9343e8282765c6e1a" + integrity sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" + glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" + micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" @@ -5675,7 +5680,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.3.1: +fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -5748,7 +5753,7 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -glob-parent@5.1.2, glob-parent@^3.1.0, glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: +glob-parent@5.1.2, glob-parent@^3.1.0, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -5808,9 +5813,9 @@ globby@11.0.1: slash "^3.0.0" globby@^11.0.2, globby@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" - integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -8010,7 +8015,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: +micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== @@ -9734,10 +9739,10 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@7.0.21: - version "7.0.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" - integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== +postcss@7.0.36, postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.36" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" + integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -9752,19 +9757,10 @@ postcss@8.2.15: nanoid "^3.1.23" source-map "^0.6.1" -postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.36" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" - integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - postcss@^8.1.0: - version "8.3.4" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.4.tgz#41ece1c43f2f7c74dc7d90144047ce052757b822" - integrity sha512-/tZY0PXExXXnNhKv3TOvZAOUYRyuqcCbBm2c17YMDK0PlVII3K7/LKdt3ScHL+hhouddjUWi+1sKDf9xXW+8YA== + version "8.3.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== dependencies: colorette "^1.2.2" nanoid "^3.1.23" @@ -10194,17 +10190,17 @@ rc-overflow@^1.0.0: rc-util "^5.5.1" rc-pagination@~3.1.2: - version "3.1.6" - resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.6.tgz#db3c06e50270b52fe272ac527c1fdc2c8d28af1f" - integrity sha512-Pb2zJEt8uxXzYCWx/2qwsYZ3vSS9Eqdw0cJBli6C58/iYhmvutSBqrBJh51Z5UzYc5ZcW5CMeP5LbbKE1J3rpw== + version "3.1.7" + resolved "https://registry.yarnpkg.com/rc-pagination/-/rc-pagination-3.1.7.tgz#13ba071a7fcb0c79896076806f3944653e7bf29e" + integrity sha512-sl0HGVhv6AsMzA5H3q7cBQcbAGj/sFjoiDSLvq3+/4IjihPqScZnSSiqR4Wu9G8RLgNjrBnGrSdTGO2Kyrt3IA== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" rc-picker@~2.5.1: - version "2.5.12" - resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.5.12.tgz#f5743276d2b29ec56a03b72b659332c06e4bb983" - integrity sha512-rUqEtpNZdPTnnCMvWWioFFcJiq110Bq7fKAS37NY4Rrd3DoXZUwyfjeCrvCWgLLO9XeYDnDr88+rhhplY7HBNA== + version "2.5.13" + resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.5.13.tgz#a2f352ebe5e067d72ffd53cc2074f8dcacbbcc7d" + integrity sha512-FbEK5tKB5RVO/Pw3DHwYdL338oVH8TdfDU69fXjeStVeiaqDHf+VgpwI5qXo5vJltmmlNcM2oAbLPq7iIKDFRw== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" @@ -10242,9 +10238,9 @@ rc-resize-observer@^1.0.0: resize-observer-polyfill "^1.5.1" rc-select@^12.0.0, rc-select@~12.1.0: - version "12.1.10" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-12.1.10.tgz#66ce43192751190b7c0e9a0ab1ef79606421ce30" - integrity sha512-LQdUhYncvcULlrNcAShYicc1obPtnNK7/rvCD+YCm0b2BLLYxl3M3b/HOX6o+ppPej+yZulkUPeU6gcgcp9nag== + version "12.1.13" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-12.1.13.tgz#c33560ccb9339d30695b52458f55efc35af35273" + integrity sha512-cPI+aesP6dgCAaey4t4upDbEukJe+XN0DK6oO/6flcCX5o28o7KNZD7JAiVtC/6fCwqwI/kSs7S/43dvHmBl+A== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -10384,9 +10380,9 @@ rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.5, rc-util@^5.0.6, rc-util@^5.0.7, shallowequal "^1.1.0" rc-virtual-list@^3.0.1, rc-virtual-list@^3.2.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.2.6.tgz#2c92a40f4425e19881b38134d6bd286a11137d2d" - integrity sha512-8FiQLDzm3c/tMX0d62SQtKDhLH7zFlSI6pWBAPt+TUntEqd3Lz9zFAmpvTu8gkvUom/HCsDSZs4wfV4wDPWC0Q== + version "3.3.0" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.3.0.tgz#2f95a6ddbbf63d78b28662b57f1e69f7472762fe" + integrity sha512-lVXpGWC6yMdwV2SHo6kc63WlqjCnb3eO72V726KA2/wh9KA6wi/swcdR3zAowuA8hJxG/lRANmY5kpLZ+Pz3iQ== dependencies: classnames "^2.2.6" rc-resize-observer "^1.0.0" @@ -10731,10 +10727,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -10979,9 +10975,9 @@ resolve-pathname@^3.0.0: integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== resolve-url-loader@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.3.tgz#49ec68340f67d8d2ab6b401948d5def3ab2d0367" - integrity sha512-WbDSNFiKPPLem1ln+EVTE+bFUBdTTytfQZWbmghroaFNFaAVmGq0Saqw6F/306CwgPXsGwXVxbODE+3xAo/YbA== + version "3.1.4" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.4.tgz#3c16caebe0b9faea9c7cc252fa49d2353c412320" + integrity sha512-D3sQ04o0eeQEySLrcz4DsX3saHfsr8/N6tfhblxgZKXxMT2Louargg12oGNfoTRLV09GXhVUe5/qgA5vdgNigg== dependencies: adjust-sourcemap-loader "3.0.0" camelcase "5.3.1" @@ -10989,7 +10985,7 @@ resolve-url-loader@^3.1.2: convert-source-map "1.7.0" es6-iterator "2.0.3" loader-utils "1.2.3" - postcss "7.0.21" + postcss "7.0.36" rework "1.0.1" rework-visit "1.0.0" source-map "0.6.1" @@ -12975,9 +12971,9 @@ whatwg-mimetype@^2.3.0: integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.6.0.tgz#27c0205a4902084b872aecb97cf0f2a7a3011f4c" - integrity sha512-os0KkeeqUOl7ccdDT1qqUcS4KH4tcBTSKK5Nl5WKb2lyxInIZ/CpjkqKa1Ss12mjfdcRX9mHmPPs7/SxG1Hbdw== + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== dependencies: lodash "^4.7.0" tr46 "^2.1.0" @@ -13275,9 +13271,9 @@ yargs-parser@^18.1.2: decamelize "^1.2.0" yargs-parser@^20.2.3: - version "20.2.7" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" - integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs@^13.3.2: version "13.3.2" diff --git a/sonar-project.properties b/sonar-project.properties index 4969baf..0bcd88f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,7 +5,7 @@ sonar.scm.provider=git # Path is relative to the sonar-project.properties file. Defaults to . sonar.sources=services/backend/src,services/contact-form/src,services/health-department/src,services/locations/src,services/scanner/src,services/webapp/src sonar.test.inclusions=**/*.spec.js,**/*.spec.jsx,**/*.test.js,**/*.test.jsx -sonar.exclusions=**/node_modules, services/locations/src/components/App/modals/CreateGroupModal/steps/AddressInput/FormFields/InputField/InputField.react.js +sonar.exclusions=**/node_modules sonar.qualitygate.wait=true -- GitLab